Multiselect appears to be working mostly correctly and is styled. It is only temp a tab, as this is going to be an intermediate/ephem widget
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
from textual.containers import Horizontal
|
from textual.containers import Horizontal, Vertical
|
||||||
from textual.css.query import NoMatches
|
from textual.css.query import NoMatches
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
@@ -16,7 +16,7 @@ class MultiAgentSelector(Widget):
|
|||||||
def __init__(self, all_agents: list[dict]):
|
def __init__(self, all_agents: list[dict]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.all_agents = all_agents
|
self.all_agents = all_agents
|
||||||
self._match_type = "fuzzy"
|
self._match_type = "exact"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def match_type(self):
|
def match_type(self):
|
||||||
@@ -27,43 +27,73 @@ class MultiAgentSelector(Widget):
|
|||||||
self._match_type = value
|
self._match_type = value
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Static("🔍 Multi-Agent Selector", id="selector_title")
|
title_text = Static("🖧 Multi-Agent Selector", id="selector_title")
|
||||||
text_area = TextArea(
|
title_text.styles.margin = (0, 0, 0, 1)
|
||||||
id="device_input",
|
yield title_text
|
||||||
placeholder="Paste device names here (one per line)",
|
|
||||||
)
|
|
||||||
text_area.styles.height = 10
|
|
||||||
text_area.styles.overflow_y = "auto"
|
|
||||||
yield text_area
|
|
||||||
|
|
||||||
with Horizontal() as switch_container:
|
with Horizontal() as main_layout:
|
||||||
switch_container.styles.height = "auto"
|
main_layout.styles.height = "auto"
|
||||||
switch_container.styles.align = ("left", "middle")
|
|
||||||
switch_label = Static("Match Type: Fuzzy", id="match_switch_label")
|
|
||||||
switch_label.styles.width = "auto"
|
|
||||||
switch_label.styles.padding = (0, 1)
|
|
||||||
yield switch_label
|
|
||||||
|
|
||||||
switch = Switch(value=False, id="match_switch")
|
# Left side - Input and controls
|
||||||
switch.styles.width = "auto"
|
with Vertical() as left_pane:
|
||||||
yield switch
|
left_pane.styles.width = "1fr"
|
||||||
|
left_pane.styles.height = "auto"
|
||||||
|
|
||||||
yield Button("Search", id="search_button")
|
text_area = TextArea(
|
||||||
|
id="device_input",
|
||||||
|
placeholder="Paste device names here (one per line). Supports wildcards: * and ?",
|
||||||
|
)
|
||||||
|
text_area.styles.height = 10
|
||||||
|
text_area.styles.overflow_y = "auto"
|
||||||
|
yield text_area
|
||||||
|
|
||||||
yield SelectionList(id="match_results")
|
with Horizontal(id="switch_search_container") as switch_search:
|
||||||
yield Static(id="unmatched_label")
|
|
||||||
|
|
||||||
yield Horizontal(
|
switch = Switch(value=False, id="match_switch")
|
||||||
Button("Select All", id="select_all"),
|
switch.styles.width = "auto"
|
||||||
Button("Select None", id="select_none"),
|
switch.styles.margin = (1, 0, 0, 0)
|
||||||
)
|
switch.styles.padding = (0, 0, 0, 0)
|
||||||
|
yield switch
|
||||||
|
|
||||||
yield Button("Continue with Selected", id="submit_selection", variant="primary")
|
switch_label = Static("Match: Exact", id="match_switch_label")
|
||||||
|
switch_label.styles.width = "auto"
|
||||||
|
switch_label.styles.margin = (2, 1, 0, 0)
|
||||||
|
yield switch_label
|
||||||
|
|
||||||
|
search = Button("🔍 Search", id="search_button")
|
||||||
|
search.styles.margin = (1, 0, 0, 0)
|
||||||
|
|
||||||
|
yield search
|
||||||
|
|
||||||
|
with Horizontal() as select_buttons:
|
||||||
|
select_buttons.styles.margin = (0, 0, 0, 0)
|
||||||
|
|
||||||
|
select_all_button = Button("✅ Select All", id="select_all")
|
||||||
|
select_all_button.styles.margin = (1, 1, 0, 1)
|
||||||
|
yield select_all_button
|
||||||
|
|
||||||
|
select_none_button = Button("🚫 Select None", id="select_none")
|
||||||
|
select_none_button.styles.margin = (1, 0, 0, 1)
|
||||||
|
yield select_none_button
|
||||||
|
|
||||||
|
submit_button = Button(
|
||||||
|
"► Continue with Selected", id="submit_selection", variant="primary"
|
||||||
|
)
|
||||||
|
submit_button.styles.margin = (0, 5, 2, 1)
|
||||||
|
submit_button.styles.padding = (0, 6, 0, 0)
|
||||||
|
yield submit_button
|
||||||
|
|
||||||
|
# Right side - Results
|
||||||
|
with Vertical() as right_pane:
|
||||||
|
right_pane.styles.width = "2fr"
|
||||||
|
|
||||||
|
yield SelectionList(id="match_results")
|
||||||
|
yield Static(id="unmatched_label")
|
||||||
|
|
||||||
def on_switch_changed(self, event: Switch.Changed):
|
def on_switch_changed(self, event: Switch.Changed):
|
||||||
self.match_type = "exact" if event.value else "fuzzy"
|
self.match_type = "fuzzy" if event.value else "exact"
|
||||||
self.query_one("#match_switch_label", Static).update(
|
self.query_one("#match_switch_label", Static).update(
|
||||||
f"Match Type: {self.match_type.capitalize()}"
|
f"Match: {self.match_type.capitalize()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed):
|
def on_button_pressed(self, event: Button.Pressed):
|
||||||
@@ -111,7 +141,28 @@ class MultiAgentSelector(Widget):
|
|||||||
matched = set()
|
matched = set()
|
||||||
unmatched = []
|
unmatched = []
|
||||||
for name in device_names:
|
for name in device_names:
|
||||||
if self.match_type == "exact":
|
# Check if the name contains wildcards
|
||||||
|
has_wildcards = "*" in name or "?" in name
|
||||||
|
|
||||||
|
if has_wildcards:
|
||||||
|
# Use regex for wildcard matching
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Escape special regex characters except * and ?
|
||||||
|
pattern = re.escape(name)
|
||||||
|
# Convert wildcards to regex
|
||||||
|
pattern = pattern.replace(r"\*", ".*").replace(r"\?", ".")
|
||||||
|
# Make it case-insensitive and match full string
|
||||||
|
regex = re.compile(f"^{pattern}$", re.IGNORECASE)
|
||||||
|
|
||||||
|
wildcard_matches = [
|
||||||
|
agent_name for agent_name in agent_names if regex.match(agent_name)
|
||||||
|
]
|
||||||
|
if wildcard_matches:
|
||||||
|
matched.update(wildcard_matches)
|
||||||
|
else:
|
||||||
|
unmatched.append(name)
|
||||||
|
elif self.match_type == "exact":
|
||||||
# Case-insensitive exact match
|
# Case-insensitive exact match
|
||||||
name_lower = name.lower()
|
name_lower = name.lower()
|
||||||
exact_match = None
|
exact_match = None
|
||||||
|
|||||||
Reference in New Issue
Block a user