Skip to content

Commit 347dd4f

Browse files
authored
feat(chat): Support input suggestion modifiers (#1854)
1 parent ddb9edd commit 347dd4f

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
different ways (see #1845 for more details):
1616
* By adding a `.suggestion` CSS class to an HTML element (e.g., `<span class="suggestion">A suggestion</span>`)
1717
* Add a `data-suggestion` attribute to an HTML element, and set the value to the input suggestion text (e.g., `<span data-suggestion="Suggestion value">Suggestion link</span>`)
18-
* To auto-submit the suggestion when clicked by the user, include the `.submit` class or the `data-suggestion-submit="true"` attribute on the HTML element.
18+
* To auto-submit the suggestion when clicked by the user, include the `.submit` class or the `data-suggestion-submit="true"` attribute on the HTML element. Alternatively, use Cmd/Ctrl + click to auto-submit any suggestion or Alt/Opt + click to apply any suggestion to the chat input without submitting.
1919

2020
* Added a new `.add_sass_layer_file()` method to `ui.Theme` that supports reading a Sass file with layer boundary comments, e.g. `/*-- scss:defaults --*/`. This format [is supported by Quarto](https://quarto.org/docs/output-formats/html-themes-more.html#bootstrap-bootswatch-layering) and makes it easier to store Sass rules and declarations that need to be woven into Shiny's Sass Bootstrap files. (#1790)
2121

js/chat/chat.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,23 +410,31 @@ class ChatContainer extends LightElement {
410410
}
411411
}
412412

413-
#onInputSuggestionClick(e: Event): void {
414-
this.#applySuggestion(e);
413+
#onInputSuggestionClick(e: MouseEvent): void {
414+
this.#onInputSuggestionEvent(e);
415415
}
416416

417417
#onInputSuggestionKeydown(e: KeyboardEvent): void {
418-
const isEnter = e.key === "Enter" || e.key === " ";
419-
if (!isEnter) return;
418+
const isEnterOrSpace = e.key === "Enter" || e.key === " ";
419+
if (!isEnterOrSpace) return;
420420

421-
this.#applySuggestion(e);
421+
this.#onInputSuggestionEvent(e);
422422
}
423423

424-
#applySuggestion(e: Event | KeyboardEvent): void {
424+
#onInputSuggestionEvent(e: MouseEvent | KeyboardEvent): void {
425425
const { suggestion, submit } = this.#getSuggestion(e.target);
426426
if (!suggestion) return;
427427

428428
e.preventDefault();
429-
this.input.setInputValue(suggestion, { submit, focus: !submit });
429+
// Cmd/Ctrl + (event) = force submitting
430+
// Alt/Opt + (event) = force setting without submitting
431+
const shouldSubmit =
432+
e.metaKey || e.ctrlKey ? true : e.altKey ? false : submit;
433+
434+
this.input.setInputValue(suggestion, {
435+
submit: shouldSubmit,
436+
focus: !shouldSubmit,
437+
});
430438
}
431439

432440
#getSuggestion(x: EventTarget | null): {

shiny/www/py-shiny/chat/chat.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shiny/www/py-shiny/chat/chat.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/playwright/shiny/components/chat/input-suggestion/test_chat_input_suggestion.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,36 @@ def test_validate_chat_input_suggestion(page: Page, local_app: ShinyAppProc) ->
5555
chat.expect_user_input("")
5656
expect(chat.loc_input_button).to_be_disabled()
5757
expect(chat.loc_input).not_to_be_focused()
58+
59+
# Reset chat
60+
chat.set_user_input("reset")
61+
chat.send_user_input()
62+
chat.expect_latest_message("You said: reset")
63+
64+
# Test keyboard modifiers - Alt + event = set but do not submit
65+
fourth.click(modifiers=["Alt"])
66+
chat.expect_user_input("this suggestion will auto-submit")
67+
chat.expect_latest_message("You said: reset")
68+
69+
fifth.focus()
70+
page.keyboard.press("Alt+Enter")
71+
chat.expect_user_input("another suggestion")
72+
chat.expect_latest_message("You said: reset")
73+
74+
# Reset chat
75+
chat.send_user_input()
76+
chat.expect_user_input("")
77+
78+
# Test keyboard modifiers - Cmd/Ctrl + event = submit the suggestion
79+
first.click(modifiers=["ControlOrMeta"])
80+
chat.expect_latest_message("You said: 1st input suggestion")
81+
chat.expect_user_input("")
82+
expect(chat.loc_input_button).to_be_disabled()
83+
expect(chat.loc_input).not_to_be_focused()
84+
85+
second.focus()
86+
page.keyboard.press("ControlOrMeta+Enter")
87+
chat.expect_latest_message("You said: The actual suggestion")
88+
chat.expect_user_input("")
89+
expect(chat.loc_input_button).to_be_disabled()
90+
expect(chat.loc_input).not_to_be_focused()

0 commit comments

Comments
 (0)