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