File size: 5,055 Bytes
5475a9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52fe59a
5475a9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52fe59a
5475a9d
 
 
 
 
 
52fe59a
 
 
 
 
 
 
 
 
 
 
5475a9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from __future__ import annotations

from typing import TYPE_CHECKING, Any, NamedTuple

import gradio as gr


if TYPE_CHECKING:
    from collections.abc import Callable, Sequence


class MutableCheckBoxGroupEntry(NamedTuple):
    """Entry of the mutable checkbox group."""

    name: str
    value: str


class MutableCheckBoxGroup(gr.Blocks):
    """Check box group with controls to add or remove values."""

    def __init__(
        self,
        values: list[MutableCheckBoxGroupEntry] | None = None,
        label: str = "Extendable List",
        new_value_label: str = "New Item Value",
        new_name_label: str = "New Item Name",
        new_value_placeholder: str = "New item value ...",
        new_name_placeholder: str = "New item name, if not given value will be used...",
        on_change: Callable[[Sequence[MutableCheckBoxGroupEntry]], None] | None = None,
    ) -> None:
        super().__init__()
        self.values = values or []

        self.label = label
        self.new_value_label = new_value_label
        self.new_name_label = new_name_label

        self.new_value_placeholder = new_value_placeholder
        self.new_name_placeholder = new_name_placeholder

        self.on_change = on_change
        self._build_interface()

    def _build_interface(self) -> None:
        # Custom CSS for vertical checkbox layout
        self.style = """
            #vertical-container .wrap {
                display: flex;
                flex-direction: column;
                gap: 8px;
            }
            #vertical-container .wrap label {
                display: flex;
                align-items: center;
                gap: 8px;
            }
        """

        with self:
            gr.Markdown(f"### {self.label}")

            # Store items in state
            self.state = gr.State(self.values)

            # Input row
            with gr.Row():
                self.input_value = gr.Textbox(
                    label=self.new_value_label,
                    placeholder=self.new_value_placeholder,
                    scale=3,
                )
                self.input_name = gr.Textbox(
                    label=self.new_name_label,
                    placeholder=self.new_name_placeholder,
                    scale=2,
                )
            with gr.Row():
                self.add_btn = gr.Button(
                    "Add",
                    variant="primary",
                    scale=1,
                )
                self.delete_btn = gr.Button(
                    "Delete Selected",
                    variant="stop",
                    scale=1,
                )

            # Vertical checkbox group
            self.items_group = gr.CheckboxGroup(
                choices=self.values,
                label="Items",
                elem_id="vertical-container",
                container=True,
            )

            # Set up event handlers
            self.add_btn.click(
                self._add_item,
                inputs=[self.state, self.input_value, self.input_name],
                outputs=[
                    self.state,
                    self.items_group,
                    self.input_value,
                    self.input_name,
                ],
            )

            self.delete_btn.click(
                self._delete_selected,
                inputs=[self.state, self.items_group],
                outputs=[self.state, self.items_group],
            )

    def get_values(self) -> Sequence[str]:
        """Get check box values."""
        return self.state.value

    def _add_item(
        self,
        items: list[MutableCheckBoxGroupEntry],
        new_value: str,
        new_name: str,
    ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any], str, str]:
        if not new_name:
            new_name = new_value

        if new_value:
            if any(entry.name == new_name for entry in items):
                raise gr.Error(
                    f"Entry with name '{new_name}' already exists",
                    duration=10,
                )
            if any(entry.value == new_value for entry in items):
                raise gr.Error(
                    f"Entry with value '{new_value}' already exists",
                    duration=10,
                )

            items = [*items, MutableCheckBoxGroupEntry(new_name, new_value)]
            if self.on_change:
                self.on_change(items)

            # State, checkbox, input_value, input_name
            return items, gr.update(choices=items), "", ""

        # State, checkbox, input_value, input_name
        return items, gr.update(), "", ""

    def _delete_selected(
        self,
        items: list[MutableCheckBoxGroupEntry],
        selected: list[str],
    ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any]]:
        updated_items = [item for item in items if item.value not in selected]
        if self.on_change:
            self.on_change(updated_items)
        return updated_items, gr.update(choices=updated_items, value=[])