Spaces:
Running
Running
| 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=[]) | |