From 1b387013650b2ff2287e509dcffab9b5f2c4570c Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Wed, 10 Apr 2024 13:16:15 -0700 Subject: [PATCH] Add custom js --- examples/reference/chat/ChatInterface.ipynb | 42 ++++++++++++++++++++- panel/chat/interface.py | 36 +++++++++++++++--- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/examples/reference/chat/ChatInterface.ipynb b/examples/reference/chat/ChatInterface.ipynb index 01f7698cd6..0faaae06a8 100644 --- a/examples/reference/chat/ChatInterface.ipynb +++ b/examples/reference/chat/ChatInterface.ipynb @@ -40,7 +40,21 @@ "* **`avatar`** (`str | bytes | BytesIO | pn.pane.Image`): The avatar to use for the user. Can be a single character text, an emoji, or anything supported by `pn.pane.Image`. If not set, uses the first character of the name.\n", "* **`reset_on_send`** (`bool`): Whether to reset the widget's value after sending a message; has no effect for `TextInput`.\n", "* **`auto_send_types`** (`tuple`): The widget types to automatically send when the user presses enter or clicks away from the widget. If not provided, defaults to `[TextInput]`.\n", - "* **`button_properties`** (`Dict[Dict[str, Any]]`): Allows addition of functionality or customization of buttons by supplying a mapping from the button name to a dictionary containing the `icon`, `callback`, and/or `post_callback` keys. If the button names correspond to default buttons (send, rerun, undo, clear), the default icon can be updated and if a `callback` key value pair is provided, the specified callback functionality runs before the existing one. For button names that don't match existing ones, new buttons are created and must include a `callback` or `post_callback` key. The provided callbacks should have a signature that accepts two positional arguments: instance (the ChatInterface instance) and event (the button click event).\n", + "* **`button_properties`** (`Dict[Dict[str, Any]]`): Allows addition of functionality or customization of buttons by supplying a mapping from the button name to a dictionary containing the `icon`, `callback`, `post_callback`, and/or `js_on_click` keys. \n", + " * If the button names correspond to default buttons\n", + "(send, rerun, undo, clear), the default icon can be\n", + "updated and if a `callback` key value pair is provided,\n", + "the specified callback functionality runs before the existing one.\n", + " * For button names that don't match existing ones,\n", + "new buttons are created and must include a\n", + "`callback`, `post_callback`, and/or `js_on_click` key.\n", + " * The provided callbacks should have a signature that accepts\n", + "two positional arguments: instance (the ChatInterface instance)\n", + "and event (the button click event).\n", + " * The `js_on_click` key should be a string of JavaScript code\n", + "to execute when the button is clicked. The `js_args` key\n", + "should be a dictionary of arguments to pass to the JavaScript\n", + "code.\n", "\n", "##### Styling\n", "\n", @@ -476,6 +490,32 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You may also use custom Javascript code with `js_on_click` for the buttons, and also set the `button_properties` after definition.\n", + "\n", + "Try typing something in the chat input, and then click the new `Help` button on the bottom right." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "chat_interface = pn.chat.ChatInterface()\n", + "chat_interface.button_properties={\n", + " \"help\": {\n", + " \"icon\": \"help\",\n", + " \"js_on_click\": \"alert(`Typed: '${chat_input.value}'`)\",\n", + " \"js_args\": {\"chat_input\": chat_interface.active_widget},\n", + " },\n", + "}\n", + "chat_interface" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/panel/chat/interface.py b/panel/chat/interface.py index 8a162e7003..43cea27b73 100644 --- a/panel/chat/interface.py +++ b/panel/chat/interface.py @@ -45,6 +45,12 @@ class _ChatButtonData: The objects to display. buttons : List The buttons to display. + callback : Callable + The callback to execute when the button is clicked. + js_on_click : str | None + The JavaScript to execute when the button is clicked. + js_args : Dict[str, Any] | None + The JavaScript arguments to pass when the button is clicked. """ index: int @@ -53,6 +59,8 @@ class _ChatButtonData: objects: List buttons: List callback: Callable + js_on_click: str | None = None + js_args: Dict[str, Any] | None = None class ChatInterface(ChatFeed): @@ -114,16 +122,25 @@ class ChatInterface(ChatFeed): button_properties = param.Dict(default={}, doc=""" Allows addition of functionality or customization of buttons by supplying a mapping from the button name to a dictionary - containing the `icon`, `callback`, and/or `post_callback` keys. + containing the `icon`, `callback`, `post_callback`, and/or `js_on_click` keys. + If the button names correspond to default buttons (send, rerun, undo, clear), the default icon can be updated and if a `callback` key value pair is provided, the specified callback functionality runs before the existing one. + For button names that don't match existing ones, - new buttons are created and must include a `callback` or `post_callback` key. + new buttons are created and must include a + `callback`, `post_callback`, and/or `js_on_click` key. + The provided callbacks should have a signature that accepts two positional arguments: instance (the ChatInterface instance) and event (the button click event). + + The `js_on_click` key should be a string of JavaScript code + to execute when the button is clicked. The `js_args` key + should be a dictionary of arguments to pass to the JavaScript + code. """) _widgets = param.Dict(default={}, allow_refs=False, doc=""" @@ -207,6 +224,7 @@ def _init_widgets(self): name = name.lower() callback = properties.get("callback") post_callback = properties.get("post_callback") + js_on_click = properties.get("js_on_click") default_properties = default_button_properties.get(name) or {} if default_properties: default_callback = default_properties["_default_callback"] @@ -222,7 +240,7 @@ def _init_widgets(self): callback = self._wrap_callbacks(post_callback=post_callback)(callback) elif callback is None and post_callback is not None: callback = post_callback - elif callback is None and post_callback is None: + elif callback is None and post_callback is None and not js_on_click: raise ValueError(f"A 'callback' key is required for the {name!r} button") icon = properties.get("icon") or default_properties.get("icon") self._button_data[name] = _ChatButtonData( @@ -232,6 +250,8 @@ def _init_widgets(self): objects=[], buttons=[], callback=callback, + js_on_click=js_on_click, + js_args=properties.get("js_args"), ) widgets = self.widgets @@ -300,8 +320,14 @@ def _init_widgets(self): ) if action != "stop": self._link_disabled_loading(button) - callback = partial(button_data.callback, self) - button.on_click(callback) + if button_data.callback: + callback = partial(button_data.callback, self) + button.on_click(callback) + if button_data.js_on_click: + button.js_on_click( + args=(button_data.js_args or {}), + code=button_data.js_on_click + ) self._buttons[action] = button button_data.buttons.append(button)