Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easier to subclass components #7730

Merged
merged 4 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions panel/chat/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ class ChatFeed(ListPanel):
_disabled_stack = param.List(doc="""
The previous disabled state of the feed.""")

_card_type: ClassVar[type[Card]] = Card
_message_type: ClassVar[type[ChatMessage]] = ChatMessage
_stylesheets: ClassVar[list[str]] = [f"{CDN_DIST}css/chat_feed.css"]

def __init__(self, *objects, **params):
Expand All @@ -238,15 +240,15 @@ def __init__(self, *objects, **params):
# forward message params to ChatMessage for convenience
message_params = params.get("message_params", {})
for param_key in params.copy():
if param_key not in self.param and param_key in ChatMessage.param:
if param_key not in self.param and param_key in self._message_type.param:
message_params[param_key] = params.pop(param_key)
params["message_params"] = message_params

super().__init__(*objects, **params)

if self.help_text:
self.objects = [
ChatMessage(
self._message_type(
self.help_text,
user="Help",
show_edit_icon=False,
Expand Down Expand Up @@ -303,7 +305,7 @@ def __init__(self, *objects, **params):
card_params.update(card_overrides)
self.link(self._chat_log, objects='objects', bidirectional=True)
# we have a card for the title
self._card = Card(
self._card = self._card_type(
self._chat_log,
VSpacer(),
**card_params
Expand Down Expand Up @@ -359,7 +361,7 @@ def _update_placeholder(self):
PLACEHOLDER_SVG, sizing_mode="fixed", width=35, height=35,
css_classes=["rotating-placeholder"]
)
self._placeholder = ChatMessage(
self._placeholder = self._message_type(
self.placeholder_text,
avatar=loading_avatar,
css_classes=["message"],
Expand Down Expand Up @@ -436,7 +438,7 @@ def _build_message(
(isinstance(user, str) and user.lower() not in (self.callback_user.lower(), "help"))
)

message = ChatMessage(**message_params)
message = self._message_type(**message_params)
message.param.watch(self._on_edit_message, "edited")
return message

Expand Down Expand Up @@ -863,7 +865,7 @@ def add_step(
if default_layout == "column":
layout = Column
elif default_layout == "card":
layout = Card
layout = self._card_type
input_layout_params["header_css_classes"] = ["card-header"]
title = layout_params.pop("title", None)
input_layout_params["header"] = HTML(
Expand Down
28 changes: 15 additions & 13 deletions panel/chat/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,12 @@ class ChatInterface(ChatFeed):
The rendered buttons.""")

_stylesheets: ClassVar[list[str]] = [f"{CDN_DIST}css/chat_interface.css"]
_input_type: ClassVar[type[WidgetBase]] = ChatAreaInput

def __init__(self, *objects, **params):
widgets = params.get("widgets")
if widgets is None:
params["widgets"] = [ChatAreaInput(placeholder="Send a message")]
params["widgets"] = [self._input_type(placeholder="Send a message")]
elif not isinstance(widgets, list):
params["widgets"] = [widgets]
active = params.pop("active", None)
Expand Down Expand Up @@ -204,15 +205,7 @@ def _update_input_width(self):
if self.show_button_name is None:
self.show_button_name = self.width is None or self.width >= 400

@param.depends("widgets", "button_properties", watch=True)
def _init_widgets(self):
"""
Initialize the input widgets.

Returns
-------
The input widgets.
"""
def _init_button_data(self):
default_button_properties = {
"send": {"icon": "send", "_default_callback": self._click_send},
"stop": {"icon": "player-stop", "_default_callback": self._click_stop},
Expand All @@ -221,7 +214,6 @@ def _init_widgets(self):
"clear": {"icon": "trash", "_default_callback": self._click_clear},
}
self._allow_revert = len(self.button_properties) == 0

button_properties = {**default_button_properties, **self.button_properties}
for index, (name, properties) in enumerate(button_properties.items()):
name = name.lower()
Expand Down Expand Up @@ -256,6 +248,16 @@ def _init_widgets(self):
js_on_click=js_on_click,
)

@param.depends("widgets", "button_properties", watch=True)
def _init_widgets(self):
"""
Initialize the input widgets.

Returns
-------
The input widgets.
"""
self._init_button_data()
widgets = self.widgets
if isinstance(self.widgets, WidgetBase):
widgets = [self.widgets]
Expand Down Expand Up @@ -290,7 +292,7 @@ def _init_widgets(self):
# TextAreaInput will trigger auto send!
auto_send = (
isinstance(widget, tuple(self.auto_send_types)) or
type(widget) in (TextInput, ChatAreaInput)
type(widget) in (TextInput, self._input_type)
)
if auto_send and widget in new_widgets:
callback = partial(self._button_data["send"].callback, self)
Expand All @@ -299,7 +301,7 @@ def _init_widgets(self):
sizing_mode="stretch_width",
css_classes=["chat-interface-input-widget"]
)
if isinstance(widget, ChatAreaInput):
if isinstance(widget, self._input_type):
self.link(widget, disabled="disabled_enter")

self._buttons = {}
Expand Down
2 changes: 1 addition & 1 deletion panel/reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,7 @@ def _set_on_model(self, msg: Mapping[str, Any], root: Model, model: Model) -> No
try:
model.update(**transformed)
finally:
if old:
if prev_changing:
self._changing[root.ref['id']] = prev_changing
else:
del self._changing[root.ref['id']]
Expand Down
2 changes: 1 addition & 1 deletion panel/viewable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,7 @@ def is_viewable_param(parameter: param.Parameter) -> bool:
"""
if isinstance(parameter, (Child, Children)):
return True
if isinstance(parameter, param.ClassSelector) and _is_viewable_class_selector(parameter):
if isinstance(parameter, param.ClassSelector) and _is_viewable_class_selector(parameter) and parameter.is_instance:
return True
if isinstance(parameter, param.List) and _is_viewable_list(parameter):
return True
Expand Down
2 changes: 1 addition & 1 deletion panel/widgets/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def _validate_options_groups(self, *events):
)

def _process_param_change(self, msg: dict[str, Any]) -> dict[str, Any]:
groups_provided = 'groups' in msg
groups_provided = msg.get('groups') is not None
msg = super()._process_param_change(msg)
if groups_provided or 'options' in msg and self.groups:
groups: dict[str, list[str | tuple[str, str]]] = self.groups
Expand Down
Loading