Skip to content

Commit

Permalink
Allow toggling CTRL+Enter vs Enter to send ChatAreaInput (#6592)
Browse files Browse the repository at this point in the history
* incomplete

* fixed spelling, better log message, weird update behavior

* update log messages

* update log messages

* update log messages

* update shift_enter_sends variable name

* working code

* fixed lint complaints

* fixed lint complaints

* removed comma

* removed comma

* removed comma

* Update examples/reference/chat/ChatAreaInput.ipynb

Co-authored-by: Andrew <[email protected]>

* Update panel/models/chatarea_input.ts

Co-authored-by: Andrew <[email protected]>

* added enter_sends test

* modified newline/send  behavior

* removed extra spaces

* added Ctrl-Enter handling when enter_sends=True

* removed space in if statement

* Update examples/reference/chat/ChatAreaInput.ipynb

Co-authored-by: Andrew <[email protected]>

* Update examples/reference/chat/ChatAreaInput.ipynb

Co-authored-by: Andrew <[email protected]>

* Update panel/chat/input.py

Co-authored-by: Andrew <[email protected]>

* Update examples/reference/chat/ChatAreaInput.ipynb

Co-authored-by: Andrew <[email protected]>

* Update examples/reference/chat/ChatAreaInput.ipynb

Co-authored-by: Andrew <[email protected]>

* Update panel/chat/input.py

Co-authored-by: Andrew <[email protected]>

* Update panel/models/chatarea_input.py

Co-authored-by: Andrew <[email protected]>

* Update panel/models/chatarea_input.py

Co-authored-by: Andrew <[email protected]>

* Update examples/reference/chat/ChatAreaInput.ipynb

* Update examples/reference/chat/ChatAreaInput.ipynb

* Update examples/reference/chat/ChatAreaInput.ipynb

* Update examples/reference/chat/ChatAreaInput.ipynb

---------

Co-authored-by: Andrew <[email protected]>
  • Loading branch information
ea42gh and ahuang11 authored Apr 9, 2024
1 parent 2f3049f commit defe76a
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 21 deletions.
33 changes: 19 additions & 14 deletions examples/reference/chat/ChatAreaInput.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
"metadata": {},
"source": [
"The `ChatAreaInput` inherits from `TextAreaInput`, allowing entering any multiline string using a text input\n",
"box, with the ability to click the \"Enter\" key to submit the message.\n",
"box, with the ability to click the \"Enter\" key or optionally the \"Ctrl-Enter\" key to submit the message.\n",
"\n",
"Whether \"Enter\" or \"Ctrl-Enter\" sends the message depends on whether the `enter_sends` parameter is set to True (the default) or False.\n",
"\n",
"Unlike TextAreaInput, the `ChatAreaInput` defaults to auto_grow=True and\n",
"max_rows=10, and the `value` is not synced to the server until the \"Enter\" key\n",
"is pressed so watch `value_input` if you need to access what's currently\n",
"max_rows=10, and the `value` is not synced to the server until the message is actually sent, so watch `value_input` if you need to access what's currently\n",
"available in the text input box.\n",
"\n",
"Lines are joined with the newline character `\\n`.\n",
"\n",
"It's primary purpose is use within the [`ChatInterface`](ChatInterface.ipynb) for a high-level, *easy to use*, *ChatGPT like* interface.\n",
"The primary purpose of `ChatAreaInput` is its use with [`ChatInterface`](ChatInterface.ipynb) for a high-level, *easy to use*, *ChatGPT like* interface.\n",
"\n",
"<img alt=\"Chat Design Specification\" src=\"../../assets/ChatDesignSpecification.png\"></img>\n",
"\n",
Expand All @@ -35,20 +36,21 @@
"\n",
"##### Core\n",
"\n",
"* **``disabled_enter``** (bool): If True, the enter key will not submit the message (clear the value).\n",
"* **``value``** (str): The value when the \"Enter\" key is pressed. Only to be used with `watch` or `bind` because the `value` resets to `\"\"` after the \"Enter\" key is pressed; use `value_input` instead to access what's currently available in the text input box.\n",
"* **``disabled_enter``** (bool): If True, disables sending the message by pressing the `enter_sends` key.\n",
"* **``enter_sends``** (bool): If True, pressing the Enter key sends the message, if False it is sent by pressing the Ctrl-Enter. Defaults to True.",
"* **``value``** (str): The value when the \"Enter\" or \"Ctrl-Enter\" key is pressed. Only to be used with `watch` or `bind` because the `value` resets to `\"\"` after the message is sent; use `value_input` instead to access what's currently available in the text input box.\n",
"* **``value_input``** (str): The current value updated on every key press.\n",
"\n",
"##### Display\n",
"\n",
"* **`auto_grow`** (boolean, default=True): Whether the TextArea should automatically grow in height to fit the content.\n",
"* **`cols`** (int, default=2): The number of columns in the text input field. \n",
"* **`cols`** (int, default=2): The number of columns in the text input field.\n",
"* **`disabled`** (boolean, default=False): Whether the widget is editable\n",
"* **`max_length`** (int, default=5000): Max character length of the input field. Defaults to 5000\n",
"* **`max_rows`** (int, default=10): The maximum number of rows in the text input field when `auto_grow=True`. \n",
"* **`max_rows`** (int, default=10): The maximum number of rows in the text input field when `auto_grow=True`.\n",
"* **`name`** (str): The title of the widget\n",
"* **`placeholder`** (str): A placeholder string displayed when no value is entered\n",
"* **`rows`** (int, default=2): The number of rows in the text input field. \n",
"* **`rows`** (int, default=2): The number of rows in the text input field.\n",
"* **`resizable`** (boolean | str, default='both'): Whether the layout is interactively resizable, and if so in which dimensions: `width`, `height`, or `both`.\n",
"\n",
"___"
Expand All @@ -60,7 +62,7 @@
"source": [
"#### Basics\n",
"\n",
"To submit a message, press the Enter key."
"To submit a message, press the \"Enter\" key if **``enter_sends``** is True (the default), else press \"Ctrl-Enter\"."
]
},
{
Expand Down Expand Up @@ -97,7 +99,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"To see what's currently typed in, use `value_input` instead because `value` will be `\"\"` besides during submission."
"To see what's currently typed in, use `value_input` instead because `value` will only be set during submission and be `\"\"` otherwise."
]
},
{
Expand All @@ -106,8 +108,11 @@
"metadata": {},
"outputs": [],
"source": [
"chat_area_input = pn.chat.ChatAreaInput(placeholder=\"Type something, leave it, and run the next cell\")\n",
"chat_area_input"
"chat_area_input = pn.chat.ChatAreaInput(enter_sends=False, # To submit a message, press Ctrl-Enter\n",
" placeholder=\"Type something, do not submit it, and run the next cell\",)\n",
"output_markdown = pn.bind(output, chat_area_input.param.value)\n",
"\n",
"pn.Row(chat_area_input, output_markdown)"
]
},
{
Expand All @@ -116,7 +121,7 @@
"metadata": {},
"outputs": [],
"source": [
"print(chat_area_input.value_input, chat_area_input.value)"
"print(f'{chat_area_input.value_input=}, {chat_area_input.value=}')"
]
}
],
Expand Down
7 changes: 6 additions & 1 deletion panel/chat/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ class ChatAreaInput(_PnTextAreaInput):

disabled_enter = param.Boolean(
default=False,
doc="If True, the enter key will not submit the message (clear the value).",
doc="If True, disables sending the message by pressing the `enter_sends` key.",
)

enter_sends = param.Boolean(
default=True,
doc="If True, pressing the Enter key sends the message, if False it is sent by pressing the Ctrl+Enter.",
)

rows = param.Integer(default=1, doc="""
Expand Down
5 changes: 4 additions & 1 deletion panel/models/chatarea_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ def __init__(self, model, value=None):
class ChatAreaInput(TextAreaInput):

disabled_enter = Bool(default=False, help="""
If True, the enter key will not submit the message (clear the value).""")
If True, disables sending the message by pressing the `enter_sends` key.""")

enter_sends = Bool(default=True, help="""
If True, pressing the Enter key sends the message, if False it is sent by pressing Ctrl+Enter""")
17 changes: 12 additions & 5 deletions panel/models/chatarea_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ export class ChatAreaInputView extends PnTextAreaInputView {
super.render()

this.el.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) {
if (!this.model.disabled_enter) {
this.model.trigger_event(new ChatMessageEvent(this.model.value_input))
this.model.value_input = ""
if (event.key === "Enter") {
if (!event.shiftKey && (event.ctrlKey != this.model.enter_sends)) {
if (!this.model.disabled_enter) {
this.model.trigger_event(new ChatMessageEvent(this.model.value_input))
this.model.value_input = ""
}
event.preventDefault()
} else if (event.ctrlKey && this.model.enter_sends) {
this.model.value_input += "\n"
event.preventDefault()
}
event.preventDefault()
}
})
}
Expand All @@ -46,6 +51,7 @@ export namespace ChatAreaInput {
export type Attrs = p.AttrsOf<Props>
export type Props = PnTextAreaInput.Props & {
disabled_enter: p.Property<boolean>
enter_sends: p.Property<boolean>
}
}

Expand All @@ -65,6 +71,7 @@ export class ChatAreaInput extends PnTextAreaInput {
this.define<ChatAreaInput.Props>(({Bool}) => {
return {
disabled_enter: [ Bool, false ],
enter_sends: [ Bool, true ],
}
})
}
Expand Down
39 changes: 39 additions & 0 deletions panel/tests/ui/chat/test_input_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def test_chat_area_input_resets_to_row(page):
# find chat area input and type a message
textbox = page.locator(".bk-input")
textbox.fill("Hello World!")

# click shift_enter 3 times
textbox.press("Shift+Enter")
textbox.press("Shift+Enter")
Expand All @@ -69,3 +70,41 @@ def test_chat_area_input_resets_to_row(page):
assert chat_area_input.value == ""
textbox_rows = textbox.evaluate("el => el.rows")
assert textbox_rows == 2


def test_chat_area_enter_sends(page):

chat_area_input = ChatAreaInput()
serve_component(page, chat_area_input)

# find chat area input
textbox = page.locator(".bk-input")

# Case enter_sends is True
chat_area_input.enter_sends = True
textbox.fill("enter_sends: True")
wait_until(lambda: chat_area_input.value_input == "enter_sends: True", page)
textbox.press("Shift+Enter")
wait_until(lambda: chat_area_input.value_input == "enter_sends: True\n", page)
textbox.press("Control+Enter")
wait_until(lambda: chat_area_input.value_input == "enter_sends: True\n\n", page)
textbox_rows = textbox.evaluate("el => el.rows")
assert textbox_rows == 3

textbox.press("Enter")
wait_until(lambda: chat_area_input.value_input == "", page)
assert chat_area_input.value == ""

# Case enter_sends is False
chat_area_input.enter_sends = False
textbox.fill("enter_sends: False")
wait_until(lambda: chat_area_input.value_input == "enter_sends: False", page)
textbox.press("Enter")
wait_until(lambda: chat_area_input.value_input == "enter_sends: False\n", page)
textbox.press("Shift+Enter")
wait_until(lambda: chat_area_input.value_input == "enter_sends: False\n\n", page)
textbox_rows = textbox.evaluate("el => el.rows")
assert textbox_rows == 3
textbox.press("Control+Enter")
wait_until(lambda: chat_area_input.value_input == "", page)
assert chat_area_input.value == ""

0 comments on commit defe76a

Please sign in to comment.