diff --git a/panel/chat/feed.py b/panel/chat/feed.py index 70cf1a0fcb..a6c30839a8 100644 --- a/panel/chat/feed.py +++ b/panel/chat/feed.py @@ -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): @@ -238,7 +240,7 @@ 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 @@ -246,7 +248,7 @@ def __init__(self, *objects, **params): if self.help_text: self.objects = [ - ChatMessage( + self._message_type( self.help_text, user="Help", show_edit_icon=False, @@ -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 @@ -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"], @@ -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 @@ -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( diff --git a/panel/chat/interface.py b/panel/chat/interface.py index 9a450457f7..ddcda3a636 100644 --- a/panel/chat/interface.py +++ b/panel/chat/interface.py @@ -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) @@ -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}, @@ -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() @@ -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] @@ -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) @@ -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 = {} diff --git a/panel/reactive.py b/panel/reactive.py index 9277898dd2..9fcabc2f9b 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -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']] diff --git a/panel/viewable.py b/panel/viewable.py index 341007e1f3..f338464a2c 100644 --- a/panel/viewable.py +++ b/panel/viewable.py @@ -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 diff --git a/panel/widgets/select.py b/panel/widgets/select.py index 6949a34b8a..6a55a2fad6 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -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