1use std::collections::HashSet;
8use unicode_width::UnicodeWidthStr;
9
10type FormValidator = fn(&str) -> Result<(), String>;
11
12pub struct TextInputState {
28 pub value: String,
30 pub cursor: usize,
32 pub placeholder: String,
34 pub max_length: Option<usize>,
36 pub validation_error: Option<String>,
38 pub masked: bool,
40}
41
42impl TextInputState {
43 pub fn new() -> Self {
45 Self {
46 value: String::new(),
47 cursor: 0,
48 placeholder: String::new(),
49 max_length: None,
50 validation_error: None,
51 masked: false,
52 }
53 }
54
55 pub fn with_placeholder(p: impl Into<String>) -> Self {
57 Self {
58 placeholder: p.into(),
59 ..Self::new()
60 }
61 }
62
63 pub fn max_length(mut self, len: usize) -> Self {
65 self.max_length = Some(len);
66 self
67 }
68
69 pub fn validate(&mut self, validator: impl Fn(&str) -> Result<(), String>) {
74 self.validation_error = validator(&self.value).err();
75 }
76}
77
78impl Default for TextInputState {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84pub struct FormField {
86 pub label: String,
88 pub input: TextInputState,
90 pub error: Option<String>,
92}
93
94impl FormField {
95 pub fn new(label: impl Into<String>) -> Self {
97 Self {
98 label: label.into(),
99 input: TextInputState::new(),
100 error: None,
101 }
102 }
103
104 pub fn placeholder(mut self, p: impl Into<String>) -> Self {
106 self.input.placeholder = p.into();
107 self
108 }
109}
110
111pub struct FormState {
113 pub fields: Vec<FormField>,
115 pub submitted: bool,
117}
118
119impl FormState {
120 pub fn new() -> Self {
122 Self {
123 fields: Vec::new(),
124 submitted: false,
125 }
126 }
127
128 pub fn field(mut self, field: FormField) -> Self {
130 self.fields.push(field);
131 self
132 }
133
134 pub fn validate(&mut self, validators: &[FormValidator]) -> bool {
138 let mut all_valid = true;
139 for (i, field) in self.fields.iter_mut().enumerate() {
140 if let Some(validator) = validators.get(i) {
141 match validator(&field.input.value) {
142 Ok(()) => field.error = None,
143 Err(msg) => {
144 field.error = Some(msg);
145 all_valid = false;
146 }
147 }
148 }
149 }
150 all_valid
151 }
152
153 pub fn value(&self, index: usize) -> &str {
155 self.fields
156 .get(index)
157 .map(|f| f.input.value.as_str())
158 .unwrap_or("")
159 }
160}
161
162impl Default for FormState {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168pub struct ToastState {
174 pub messages: Vec<ToastMessage>,
176}
177
178pub struct ToastMessage {
180 pub text: String,
182 pub level: ToastLevel,
184 pub created_tick: u64,
186 pub duration_ticks: u64,
188}
189
190pub enum ToastLevel {
192 Info,
194 Success,
196 Warning,
198 Error,
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum AlertLevel {
204 Info,
205 Success,
206 Warning,
207 Error,
208}
209
210impl ToastState {
211 pub fn new() -> Self {
213 Self {
214 messages: Vec::new(),
215 }
216 }
217
218 pub fn info(&mut self, text: impl Into<String>, tick: u64) {
220 self.push(text, ToastLevel::Info, tick, 30);
221 }
222
223 pub fn success(&mut self, text: impl Into<String>, tick: u64) {
225 self.push(text, ToastLevel::Success, tick, 30);
226 }
227
228 pub fn warning(&mut self, text: impl Into<String>, tick: u64) {
230 self.push(text, ToastLevel::Warning, tick, 50);
231 }
232
233 pub fn error(&mut self, text: impl Into<String>, tick: u64) {
235 self.push(text, ToastLevel::Error, tick, 80);
236 }
237
238 pub fn push(
240 &mut self,
241 text: impl Into<String>,
242 level: ToastLevel,
243 tick: u64,
244 duration_ticks: u64,
245 ) {
246 self.messages.push(ToastMessage {
247 text: text.into(),
248 level,
249 created_tick: tick,
250 duration_ticks,
251 });
252 }
253
254 pub fn cleanup(&mut self, current_tick: u64) {
258 self.messages.retain(|message| {
259 current_tick < message.created_tick.saturating_add(message.duration_ticks)
260 });
261 }
262}
263
264impl Default for ToastState {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270pub struct TextareaState {
275 pub lines: Vec<String>,
277 pub cursor_row: usize,
279 pub cursor_col: usize,
281 pub max_length: Option<usize>,
283 pub wrap_width: Option<u32>,
285 pub scroll_offset: usize,
287}
288
289impl TextareaState {
290 pub fn new() -> Self {
292 Self {
293 lines: vec![String::new()],
294 cursor_row: 0,
295 cursor_col: 0,
296 max_length: None,
297 wrap_width: None,
298 scroll_offset: 0,
299 }
300 }
301
302 pub fn value(&self) -> String {
304 self.lines.join("\n")
305 }
306
307 pub fn set_value(&mut self, text: impl Into<String>) {
311 let value = text.into();
312 self.lines = value.split('\n').map(str::to_string).collect();
313 if self.lines.is_empty() {
314 self.lines.push(String::new());
315 }
316 self.cursor_row = 0;
317 self.cursor_col = 0;
318 self.scroll_offset = 0;
319 }
320
321 pub fn max_length(mut self, len: usize) -> Self {
323 self.max_length = Some(len);
324 self
325 }
326
327 pub fn word_wrap(mut self, width: u32) -> Self {
329 self.wrap_width = Some(width);
330 self
331 }
332}
333
334impl Default for TextareaState {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340pub struct SpinnerState {
346 chars: Vec<char>,
347}
348
349impl SpinnerState {
350 pub fn dots() -> Self {
354 Self {
355 chars: vec!['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
356 }
357 }
358
359 pub fn line() -> Self {
363 Self {
364 chars: vec!['|', '/', '-', '\\'],
365 }
366 }
367
368 pub fn frame(&self, tick: u64) -> char {
370 if self.chars.is_empty() {
371 return ' ';
372 }
373 self.chars[tick as usize % self.chars.len()]
374 }
375}
376
377impl Default for SpinnerState {
378 fn default() -> Self {
379 Self::dots()
380 }
381}
382
383pub struct ListState {
388 pub items: Vec<String>,
390 pub selected: usize,
392 pub filter: String,
394 view_indices: Vec<usize>,
395}
396
397impl ListState {
398 pub fn new(items: Vec<impl Into<String>>) -> Self {
400 let len = items.len();
401 Self {
402 items: items.into_iter().map(Into::into).collect(),
403 selected: 0,
404 filter: String::new(),
405 view_indices: (0..len).collect(),
406 }
407 }
408
409 pub fn set_filter(&mut self, filter: impl Into<String>) {
413 self.filter = filter.into();
414 self.rebuild_view();
415 }
416
417 pub fn visible_indices(&self) -> &[usize] {
419 &self.view_indices
420 }
421
422 pub fn selected_item(&self) -> Option<&str> {
424 let data_idx = *self.view_indices.get(self.selected)?;
425 self.items.get(data_idx).map(String::as_str)
426 }
427
428 fn rebuild_view(&mut self) {
429 let tokens: Vec<String> = self
430 .filter
431 .split_whitespace()
432 .map(|t| t.to_lowercase())
433 .collect();
434 self.view_indices = if tokens.is_empty() {
435 (0..self.items.len()).collect()
436 } else {
437 (0..self.items.len())
438 .filter(|&i| {
439 tokens
440 .iter()
441 .all(|token| self.items[i].to_lowercase().contains(token.as_str()))
442 })
443 .collect()
444 };
445 if !self.view_indices.is_empty() && self.selected >= self.view_indices.len() {
446 self.selected = self.view_indices.len() - 1;
447 }
448 }
449}
450
451pub struct TabsState {
456 pub labels: Vec<String>,
458 pub selected: usize,
460}
461
462impl TabsState {
463 pub fn new(labels: Vec<impl Into<String>>) -> Self {
465 Self {
466 labels: labels.into_iter().map(Into::into).collect(),
467 selected: 0,
468 }
469 }
470
471 pub fn selected_label(&self) -> Option<&str> {
473 self.labels.get(self.selected).map(String::as_str)
474 }
475}
476
477pub struct TableState {
483 pub headers: Vec<String>,
485 pub rows: Vec<Vec<String>>,
487 pub selected: usize,
489 column_widths: Vec<u32>,
490 dirty: bool,
491 pub sort_column: Option<usize>,
493 pub sort_ascending: bool,
495 pub filter: String,
497 pub page: usize,
499 pub page_size: usize,
501 view_indices: Vec<usize>,
502}
503
504impl TableState {
505 pub fn new(headers: Vec<impl Into<String>>, rows: Vec<Vec<impl Into<String>>>) -> Self {
507 let headers: Vec<String> = headers.into_iter().map(Into::into).collect();
508 let rows: Vec<Vec<String>> = rows
509 .into_iter()
510 .map(|r| r.into_iter().map(Into::into).collect())
511 .collect();
512 let mut state = Self {
513 headers,
514 rows,
515 selected: 0,
516 column_widths: Vec::new(),
517 dirty: true,
518 sort_column: None,
519 sort_ascending: true,
520 filter: String::new(),
521 page: 0,
522 page_size: 0,
523 view_indices: Vec::new(),
524 };
525 state.rebuild_view();
526 state.recompute_widths();
527 state
528 }
529
530 pub fn set_rows(&mut self, rows: Vec<Vec<impl Into<String>>>) {
535 self.rows = rows
536 .into_iter()
537 .map(|r| r.into_iter().map(Into::into).collect())
538 .collect();
539 self.rebuild_view();
540 }
541
542 pub fn toggle_sort(&mut self, column: usize) {
544 if self.sort_column == Some(column) {
545 self.sort_ascending = !self.sort_ascending;
546 } else {
547 self.sort_column = Some(column);
548 self.sort_ascending = true;
549 }
550 self.rebuild_view();
551 }
552
553 pub fn sort_by(&mut self, column: usize) {
555 self.sort_column = Some(column);
556 self.sort_ascending = true;
557 self.rebuild_view();
558 }
559
560 pub fn set_filter(&mut self, filter: impl Into<String>) {
564 self.filter = filter.into();
565 self.page = 0;
566 self.rebuild_view();
567 }
568
569 pub fn clear_sort(&mut self) {
571 self.sort_column = None;
572 self.sort_ascending = true;
573 self.rebuild_view();
574 }
575
576 pub fn next_page(&mut self) {
578 if self.page_size == 0 {
579 return;
580 }
581 let last_page = self.total_pages().saturating_sub(1);
582 self.page = (self.page + 1).min(last_page);
583 }
584
585 pub fn prev_page(&mut self) {
587 self.page = self.page.saturating_sub(1);
588 }
589
590 pub fn total_pages(&self) -> usize {
592 if self.page_size == 0 {
593 return 1;
594 }
595
596 let len = self.view_indices.len();
597 if len == 0 {
598 1
599 } else {
600 len.div_ceil(self.page_size)
601 }
602 }
603
604 pub fn visible_indices(&self) -> &[usize] {
606 &self.view_indices
607 }
608
609 pub fn selected_row(&self) -> Option<&[String]> {
611 if self.view_indices.is_empty() {
612 return None;
613 }
614 let data_idx = self.view_indices.get(self.selected)?;
615 self.rows.get(*data_idx).map(|r| r.as_slice())
616 }
617
618 fn rebuild_view(&mut self) {
620 let mut indices: Vec<usize> = (0..self.rows.len()).collect();
621
622 let tokens: Vec<String> = self
623 .filter
624 .split_whitespace()
625 .map(|t| t.to_lowercase())
626 .collect();
627 if !tokens.is_empty() {
628 indices.retain(|&idx| {
629 let row = match self.rows.get(idx) {
630 Some(r) => r,
631 None => return false,
632 };
633 tokens.iter().all(|token| {
634 row.iter()
635 .any(|cell| cell.to_lowercase().contains(token.as_str()))
636 })
637 });
638 }
639
640 if let Some(column) = self.sort_column {
641 indices.sort_by(|a, b| {
642 let left = self
643 .rows
644 .get(*a)
645 .and_then(|row| row.get(column))
646 .map(String::as_str)
647 .unwrap_or("");
648 let right = self
649 .rows
650 .get(*b)
651 .and_then(|row| row.get(column))
652 .map(String::as_str)
653 .unwrap_or("");
654
655 match (left.parse::<f64>(), right.parse::<f64>()) {
656 (Ok(l), Ok(r)) => l.partial_cmp(&r).unwrap_or(std::cmp::Ordering::Equal),
657 _ => left.to_lowercase().cmp(&right.to_lowercase()),
658 }
659 });
660
661 if !self.sort_ascending {
662 indices.reverse();
663 }
664 }
665
666 self.view_indices = indices;
667
668 if self.page_size > 0 {
669 self.page = self.page.min(self.total_pages().saturating_sub(1));
670 } else {
671 self.page = 0;
672 }
673
674 self.selected = self.selected.min(self.view_indices.len().saturating_sub(1));
675 self.dirty = true;
676 }
677
678 pub(crate) fn recompute_widths(&mut self) {
679 let col_count = self.headers.len();
680 self.column_widths = vec![0u32; col_count];
681 for (i, header) in self.headers.iter().enumerate() {
682 let mut width = UnicodeWidthStr::width(header.as_str()) as u32;
683 if self.sort_column == Some(i) {
684 width += 2;
685 }
686 self.column_widths[i] = width;
687 }
688 for row in &self.rows {
689 for (i, cell) in row.iter().enumerate() {
690 if i < col_count {
691 let w = UnicodeWidthStr::width(cell.as_str()) as u32;
692 self.column_widths[i] = self.column_widths[i].max(w);
693 }
694 }
695 }
696 self.dirty = false;
697 }
698
699 pub(crate) fn column_widths(&self) -> &[u32] {
700 &self.column_widths
701 }
702
703 pub(crate) fn is_dirty(&self) -> bool {
704 self.dirty
705 }
706}
707
708pub struct ScrollState {
714 pub offset: usize,
716 content_height: u32,
717 viewport_height: u32,
718}
719
720impl ScrollState {
721 pub fn new() -> Self {
723 Self {
724 offset: 0,
725 content_height: 0,
726 viewport_height: 0,
727 }
728 }
729
730 pub fn can_scroll_up(&self) -> bool {
732 self.offset > 0
733 }
734
735 pub fn can_scroll_down(&self) -> bool {
737 (self.offset as u32) + self.viewport_height < self.content_height
738 }
739
740 pub fn content_height(&self) -> u32 {
742 self.content_height
743 }
744
745 pub fn viewport_height(&self) -> u32 {
747 self.viewport_height
748 }
749
750 pub fn progress(&self) -> f32 {
752 let max = self.content_height.saturating_sub(self.viewport_height);
753 if max == 0 {
754 0.0
755 } else {
756 self.offset as f32 / max as f32
757 }
758 }
759
760 pub fn scroll_up(&mut self, amount: usize) {
762 self.offset = self.offset.saturating_sub(amount);
763 }
764
765 pub fn scroll_down(&mut self, amount: usize) {
767 let max_offset = self.content_height.saturating_sub(self.viewport_height) as usize;
768 self.offset = (self.offset + amount).min(max_offset);
769 }
770
771 pub(crate) fn set_bounds(&mut self, content_height: u32, viewport_height: u32) {
772 self.content_height = content_height;
773 self.viewport_height = viewport_height;
774 }
775}
776
777impl Default for ScrollState {
778 fn default() -> Self {
779 Self::new()
780 }
781}
782
783#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
793pub enum ButtonVariant {
794 #[default]
796 Default,
797 Primary,
799 Danger,
801 Outline,
803}
804
805#[derive(Debug, Clone, Copy, PartialEq, Eq)]
806pub enum Trend {
807 Up,
808 Down,
809}
810
811pub struct SelectState {
818 pub items: Vec<String>,
819 pub selected: usize,
820 pub open: bool,
821 pub placeholder: String,
822 cursor: usize,
823}
824
825impl SelectState {
826 pub fn new(items: Vec<impl Into<String>>) -> Self {
827 Self {
828 items: items.into_iter().map(Into::into).collect(),
829 selected: 0,
830 open: false,
831 placeholder: String::new(),
832 cursor: 0,
833 }
834 }
835
836 pub fn placeholder(mut self, p: impl Into<String>) -> Self {
837 self.placeholder = p.into();
838 self
839 }
840
841 pub fn selected_item(&self) -> Option<&str> {
842 self.items.get(self.selected).map(String::as_str)
843 }
844
845 pub(crate) fn cursor(&self) -> usize {
846 self.cursor
847 }
848
849 pub(crate) fn set_cursor(&mut self, c: usize) {
850 self.cursor = c;
851 }
852}
853
854pub struct RadioState {
860 pub items: Vec<String>,
861 pub selected: usize,
862}
863
864impl RadioState {
865 pub fn new(items: Vec<impl Into<String>>) -> Self {
866 Self {
867 items: items.into_iter().map(Into::into).collect(),
868 selected: 0,
869 }
870 }
871
872 pub fn selected_item(&self) -> Option<&str> {
873 self.items.get(self.selected).map(String::as_str)
874 }
875}
876
877pub struct MultiSelectState {
883 pub items: Vec<String>,
884 pub cursor: usize,
885 pub selected: HashSet<usize>,
886}
887
888impl MultiSelectState {
889 pub fn new(items: Vec<impl Into<String>>) -> Self {
890 Self {
891 items: items.into_iter().map(Into::into).collect(),
892 cursor: 0,
893 selected: HashSet::new(),
894 }
895 }
896
897 pub fn selected_items(&self) -> Vec<&str> {
898 let mut indices: Vec<usize> = self.selected.iter().copied().collect();
899 indices.sort();
900 indices
901 .iter()
902 .filter_map(|&i| self.items.get(i).map(String::as_str))
903 .collect()
904 }
905
906 pub fn toggle(&mut self, index: usize) {
907 if self.selected.contains(&index) {
908 self.selected.remove(&index);
909 } else {
910 self.selected.insert(index);
911 }
912 }
913}
914
915pub struct TreeNode {
919 pub label: String,
920 pub children: Vec<TreeNode>,
921 pub expanded: bool,
922}
923
924impl TreeNode {
925 pub fn new(label: impl Into<String>) -> Self {
926 Self {
927 label: label.into(),
928 children: Vec::new(),
929 expanded: false,
930 }
931 }
932
933 pub fn expanded(mut self) -> Self {
934 self.expanded = true;
935 self
936 }
937
938 pub fn children(mut self, children: Vec<TreeNode>) -> Self {
939 self.children = children;
940 self
941 }
942
943 pub fn is_leaf(&self) -> bool {
944 self.children.is_empty()
945 }
946
947 fn flatten(&self, depth: usize, out: &mut Vec<FlatTreeEntry>) {
948 out.push(FlatTreeEntry {
949 depth,
950 label: self.label.clone(),
951 is_leaf: self.is_leaf(),
952 expanded: self.expanded,
953 });
954 if self.expanded {
955 for child in &self.children {
956 child.flatten(depth + 1, out);
957 }
958 }
959 }
960}
961
962pub(crate) struct FlatTreeEntry {
963 pub depth: usize,
964 pub label: String,
965 pub is_leaf: bool,
966 pub expanded: bool,
967}
968
969pub struct TreeState {
971 pub nodes: Vec<TreeNode>,
972 pub selected: usize,
973}
974
975impl TreeState {
976 pub fn new(nodes: Vec<TreeNode>) -> Self {
977 Self { nodes, selected: 0 }
978 }
979
980 pub(crate) fn flatten(&self) -> Vec<FlatTreeEntry> {
981 let mut entries = Vec::new();
982 for node in &self.nodes {
983 node.flatten(0, &mut entries);
984 }
985 entries
986 }
987
988 pub(crate) fn toggle_at(&mut self, flat_index: usize) {
989 let mut counter = 0usize;
990 Self::toggle_recursive(&mut self.nodes, flat_index, &mut counter);
991 }
992
993 fn toggle_recursive(nodes: &mut [TreeNode], target: usize, counter: &mut usize) -> bool {
994 for node in nodes.iter_mut() {
995 if *counter == target {
996 if !node.is_leaf() {
997 node.expanded = !node.expanded;
998 }
999 return true;
1000 }
1001 *counter += 1;
1002 if node.expanded && Self::toggle_recursive(&mut node.children, target, counter) {
1003 return true;
1004 }
1005 }
1006 false
1007 }
1008}
1009
1010pub struct PaletteCommand {
1014 pub label: String,
1015 pub description: String,
1016 pub shortcut: Option<String>,
1017}
1018
1019impl PaletteCommand {
1020 pub fn new(label: impl Into<String>, description: impl Into<String>) -> Self {
1021 Self {
1022 label: label.into(),
1023 description: description.into(),
1024 shortcut: None,
1025 }
1026 }
1027
1028 pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1029 self.shortcut = Some(s.into());
1030 self
1031 }
1032}
1033
1034pub struct CommandPaletteState {
1038 pub commands: Vec<PaletteCommand>,
1039 pub input: String,
1040 pub cursor: usize,
1041 pub open: bool,
1042 selected: usize,
1043}
1044
1045impl CommandPaletteState {
1046 pub fn new(commands: Vec<PaletteCommand>) -> Self {
1047 Self {
1048 commands,
1049 input: String::new(),
1050 cursor: 0,
1051 open: false,
1052 selected: 0,
1053 }
1054 }
1055
1056 pub fn toggle(&mut self) {
1057 self.open = !self.open;
1058 if self.open {
1059 self.input.clear();
1060 self.cursor = 0;
1061 self.selected = 0;
1062 }
1063 }
1064
1065 pub(crate) fn filtered_indices(&self) -> Vec<usize> {
1066 let tokens: Vec<String> = self
1067 .input
1068 .split_whitespace()
1069 .map(|t| t.to_lowercase())
1070 .collect();
1071 if tokens.is_empty() {
1072 return (0..self.commands.len()).collect();
1073 }
1074 self.commands
1075 .iter()
1076 .enumerate()
1077 .filter(|(_, cmd)| {
1078 let label = cmd.label.to_lowercase();
1079 let desc = cmd.description.to_lowercase();
1080 tokens
1081 .iter()
1082 .all(|token| label.contains(token.as_str()) || desc.contains(token.as_str()))
1083 })
1084 .map(|(i, _)| i)
1085 .collect()
1086 }
1087
1088 pub(crate) fn selected(&self) -> usize {
1089 self.selected
1090 }
1091
1092 pub(crate) fn set_selected(&mut self, s: usize) {
1093 self.selected = s;
1094 }
1095}
1096
1097pub struct StreamingTextState {
1102 pub content: String,
1104 pub streaming: bool,
1106 pub(crate) cursor_visible: bool,
1108 pub(crate) cursor_tick: u64,
1109}
1110
1111impl StreamingTextState {
1112 pub fn new() -> Self {
1114 Self {
1115 content: String::new(),
1116 streaming: false,
1117 cursor_visible: true,
1118 cursor_tick: 0,
1119 }
1120 }
1121
1122 pub fn push(&mut self, chunk: &str) {
1124 self.content.push_str(chunk);
1125 }
1126
1127 pub fn finish(&mut self) {
1129 self.streaming = false;
1130 }
1131
1132 pub fn start(&mut self) {
1134 self.content.clear();
1135 self.streaming = true;
1136 self.cursor_visible = true;
1137 self.cursor_tick = 0;
1138 }
1139
1140 pub fn clear(&mut self) {
1142 self.content.clear();
1143 self.streaming = false;
1144 self.cursor_visible = true;
1145 self.cursor_tick = 0;
1146 }
1147}
1148
1149impl Default for StreamingTextState {
1150 fn default() -> Self {
1151 Self::new()
1152 }
1153}
1154
1155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1157pub enum ApprovalAction {
1158 Pending,
1160 Approved,
1162 Rejected,
1164}
1165
1166pub struct ToolApprovalState {
1172 pub tool_name: String,
1174 pub description: String,
1176 pub action: ApprovalAction,
1178}
1179
1180impl ToolApprovalState {
1181 pub fn new(tool_name: impl Into<String>, description: impl Into<String>) -> Self {
1183 Self {
1184 tool_name: tool_name.into(),
1185 description: description.into(),
1186 action: ApprovalAction::Pending,
1187 }
1188 }
1189
1190 pub fn reset(&mut self) {
1192 self.action = ApprovalAction::Pending;
1193 }
1194}
1195
1196#[derive(Debug, Clone)]
1198pub struct ContextItem {
1199 pub label: String,
1201 pub tokens: usize,
1203}
1204
1205impl ContextItem {
1206 pub fn new(label: impl Into<String>, tokens: usize) -> Self {
1208 Self {
1209 label: label.into(),
1210 tokens,
1211 }
1212 }
1213}