Josep Pon Farreny commited on
Commit
7e71135
·
1 Parent(s): 7fa7ab9

feat: Add mutable list of checkboxes

Browse files
tdagent/grcomponents/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .mcbgroup import MutableCheckBoxGroup, MutableCheckBoxGroupEntry
tdagent/grcomponents/mcbgroup.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, NamedTuple
4
+
5
+ import gradio as gr
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Callable, Sequence
10
+
11
+
12
+ class MutableCheckBoxGroupEntry(NamedTuple):
13
+ """Entry of the mutable checkbox group."""
14
+
15
+ name: str
16
+ value: str
17
+
18
+
19
+ class MutableCheckBoxGroup(gr.Blocks):
20
+ """Check box group with controls to add or remove values."""
21
+
22
+ def __init__( # noqa: PLR0913
23
+ self,
24
+ values: list[MutableCheckBoxGroupEntry] | None = None,
25
+ label: str = "Extendable List",
26
+ new_value_label: str = "New Item Value",
27
+ new_name_label: str = "New Item Name",
28
+ new_value_placeholder: str = "New item value ...",
29
+ new_name_placeholder: str = "New item name, if not given value will be used...",
30
+ on_change: Callable[[Sequence[MutableCheckBoxGroupEntry]], None] | None = None,
31
+ ) -> None:
32
+ super().__init__()
33
+ self.values = values or []
34
+
35
+ self.label = label
36
+ self.new_value_label = new_value_label
37
+ self.new_name_label = new_name_label
38
+
39
+ self.new_value_placeholder = new_value_placeholder
40
+ self.new_name_placeholder = new_name_placeholder
41
+
42
+ self.on_change = on_change
43
+ self._build_interface()
44
+
45
+ def _build_interface(self) -> None:
46
+ # Custom CSS for vertical checkbox layout
47
+ self.style = """
48
+ #vertical-container .wrap {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: 8px;
52
+ }
53
+ #vertical-container .wrap label {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ }
58
+ """
59
+
60
+ with self:
61
+ gr.Markdown(f"### {self.label}")
62
+
63
+ # Store items in state
64
+ self.state = gr.State(self.values)
65
+
66
+ # Input row
67
+ with gr.Row():
68
+ self.input_value = gr.Textbox(
69
+ label=self.new_value_label,
70
+ placeholder=self.new_value_placeholder,
71
+ scale=4,
72
+ )
73
+ self.input_name = gr.Textbox(
74
+ label=self.new_name_label,
75
+ placeholder=self.new_name_placeholder,
76
+ scale=2,
77
+ )
78
+ with gr.Column():
79
+ self.add_btn = gr.Button("Add", variant="primary", scale=1)
80
+ self.delete_btn = gr.Button("Delete Selected", variant="stop")
81
+
82
+ # Vertical checkbox group
83
+ self.items_group = gr.CheckboxGroup(
84
+ choices=self.values,
85
+ label="Items",
86
+ elem_id="vertical-container",
87
+ container=True,
88
+ )
89
+
90
+ # Set up event handlers
91
+ self.add_btn.click(
92
+ self._add_item,
93
+ inputs=[self.state, self.input_value, self.input_name],
94
+ outputs=[
95
+ self.state,
96
+ self.items_group,
97
+ self.input_value,
98
+ self.input_name,
99
+ ],
100
+ )
101
+
102
+ self.delete_btn.click(
103
+ self._delete_selected,
104
+ inputs=[self.state, self.items_group],
105
+ outputs=[self.state, self.items_group],
106
+ )
107
+
108
+ def get_values(self) -> Sequence[str]:
109
+ """Get check box values."""
110
+ return self.state.value
111
+
112
+ def _add_item(
113
+ self,
114
+ items: list[MutableCheckBoxGroupEntry],
115
+ new_value: str,
116
+ new_name: str,
117
+ ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any], str, str]:
118
+ if not new_name:
119
+ new_name = new_value
120
+
121
+ if new_value:
122
+ if any(entry.name == new_name for entry in items):
123
+ raise gr.Error(
124
+ f"Entry with name '{new_name}' already exists",
125
+ duration=10,
126
+ )
127
+ if any(entry.value == new_value for entry in items):
128
+ raise gr.Error(
129
+ f"Entry with value '{new_value}' already exists",
130
+ duration=10,
131
+ )
132
+
133
+ items = [*items, MutableCheckBoxGroupEntry(new_name, new_value)]
134
+ if self.on_change:
135
+ self.on_change(items)
136
+
137
+ # State, checkbox, input_value, input_name
138
+ return items, gr.update(choices=items), "", ""
139
+
140
+ # State, checkbox, input_value, input_name
141
+ return items, gr.update(), "", ""
142
+
143
+ def _delete_selected(
144
+ self,
145
+ items: list[MutableCheckBoxGroupEntry],
146
+ selected: list[str],
147
+ ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any]]:
148
+ updated_items = [item for item in items if item.value not in selected]
149
+ if self.on_change:
150
+ self.on_change(updated_items)
151
+ return updated_items, gr.update(choices=updated_items, value=[])