Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
39 changes: 27 additions & 12 deletions js/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type ShinyChatMessage = {
type UpdateUserInput = {
value?: string;
placeholder?: string;
submit?: false;
focus?: false;
};

// https://github.com/microsoft/TypeScript/issues/28357#issuecomment-748550734
Expand Down Expand Up @@ -113,6 +115,11 @@ class ChatMessages extends LightElement {
}
}

interface ChatInputSetInputOptions {
submit?: boolean;
focus?: boolean;
}

class ChatInput extends LightElement {
private _disabled = false;

Expand Down Expand Up @@ -208,7 +215,7 @@ class ChatInput extends LightElement {
this.#onInput();
}

#sendInput(): void {
#sendInput(focus = true): void {
if (this.valueIsEmpty) return;
if (this.disabled) return;

Expand All @@ -225,18 +232,25 @@ class ChatInput extends LightElement {
this.setInputValue("");
this.disabled = true;

this.textarea.focus();
if (focus) this.textarea.focus();
}

setInputValue(value: string, submit = false): void {
setInputValue(
value: string,
{ submit = false, focus = false }: ChatInputSetInputOptions = {}
): void {
this.textarea.value = value;

// Simulate an input event (to trigger the textarea autoresize)
const inputEvent = new Event("input", { bubbles: true, cancelable: true });
this.textarea.dispatchEvent(inputEvent);

if (submit) {
this.#sendInput();
this.#sendInput(focus);
}

if (focus) {
this.textarea.focus();
}
}
}
Expand Down Expand Up @@ -383,31 +397,32 @@ class ChatContainer extends LightElement {
}

#onUpdateUserInput(event: CustomEvent<UpdateUserInput>): void {
const { value, placeholder } = event.detail;
const { value, placeholder, submit, focus } = event.detail;
if (value !== undefined) {
this.input.setInputValue(value);
this.input.setInputValue(value, { submit, focus });
}
if (placeholder !== undefined) {
this.input.placeholder = placeholder;
}
}

#onInputSuggestionClick(e: Event): void {
const { suggestion, submit } = this.#getSuggestion(e.target);
if (!suggestion) return;

e.preventDefault();
this.input.setInputValue(suggestion, submit);
this.#applySuggestion(e);
}

#onInputSuggestionKeydown(e: KeyboardEvent): void {
const isEnter = e.key === "Enter" || e.key === " ";
if (!isEnter) return;

this.#applySuggestion(e);
}

#applySuggestion(e: Event | KeyboardEvent): void {
const { suggestion, submit } = this.#getSuggestion(e.target);
if (!suggestion) return;

e.preventDefault();
this.input.setInputValue(suggestion, submit);
this.input.setInputValue(suggestion, { submit, focus: !submit });
}

#getSuggestion(x: EventTarget | null): {
Expand Down
24 changes: 18 additions & 6 deletions shiny/ui/_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,6 @@ async def _transform_message(
chunk: ChunkOption = False,
chunk_content: str | None = None,
) -> TransformedMessage | None:

res = as_transformed_message(message)
key = res["transform_key"]

Expand Down Expand Up @@ -835,7 +834,6 @@ def _store_message(
chunk: ChunkOption = False,
index: int | None = None,
) -> None:

# Don't actually store chunks until the end
if chunk is True or chunk == "start":
return None
Expand All @@ -861,7 +859,6 @@ def _trim_messages(
token_limits: tuple[int, int],
format: MISSING_TYPE | ProviderMessageFormat,
) -> tuple[TransformedMessage, ...]:

n_total, n_reserve = token_limits
if n_total <= n_reserve:
raise ValueError(
Expand Down Expand Up @@ -922,7 +919,6 @@ def _trim_anthropic_messages(
self,
messages: tuple[TransformedMessage, ...],
) -> tuple[TransformedMessage, ...]:

if any(m["role"] == "system" for m in messages):
raise ValueError(
"Anthropic requires a system prompt to be specified in it's `.create()` method "
Expand Down Expand Up @@ -982,7 +978,12 @@ def _user_input(self) -> str:
return cast(str, self._session.input[id]())

def update_user_input(
self, *, value: str | None = None, placeholder: str | None = None
self,
*,
value: str | None = None,
placeholder: str | None = None,
submit: bool = False,
focus: bool = False,
):
"""
Update the user input.
Expand All @@ -993,9 +994,20 @@ def update_user_input(
The value to set the user input to.
placeholder
The placeholder text for the user input.
submit
Whether to automatically submit the text for the user.
focus
Whether to move focus to the input element.
"""

obj = _utils.drop_none({"value": value, "placeholder": placeholder})
obj = _utils.drop_none(
{
"value": value,
"placeholder": placeholder,
"submit": submit,
"focus": focus,
}
)

_utils.run_coro_sync(
self._session.send_custom_message(
Expand Down
Loading
Loading