Skip to content

Commit

Permalink
feat: Add value prop to ui.chatbot_card #2367
Browse files Browse the repository at this point in the history
  • Loading branch information
marek-mihok committed Aug 16, 2024
1 parent c524498 commit d6ceb3b
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 4 deletions.
56 changes: 56 additions & 0 deletions py/examples/chatbot_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Chatbot / Value
# Use this card for chatbot interactions.
# #chatbot #value
# ---
from h2o_wave import main, app, Q, ui, data

templates = {
'template_poem': 'Write a poem about [SUBJECT]. Be creative and use [EMOTION] words.',
'template_summarize': 'Summarize the following text with [WORD_COUNT] words maximum while keeping all of the important information: [PASTE_YOUR_TEXT_HERE]',
'template_professional': 'Rewrite the following text professionally while sounding like a high-class writer: [PASTE_YOUR_TEXT_HERE]',
}

template_choices = [
ui.choice('template_poem', 'Write a poem'),
ui.choice('template_summarize', 'Summarize'),
ui.choice('template_professional', 'Rewrite professionally'),
]

@app('/demo')
async def serve(q: Q):
if not q.client.initialized:
q.page['dropdown'] = ui.form_card(
box='6 1 2 2',
items=[
ui.dropdown(
name='dropdown',
label='Select a template',
value="template_poem",
choices=template_choices,
trigger=True)
]
)
q.page['example'] = ui.chatbot_card(
box='1 1 5 5',
data=data('content from_user', t='list'),
name='chatbot',
value=templates['template_poem']
)
q.client.initialized = True

# Handle template change.
if q.args.dropdown:
if q.args.dropdown == 'template_poem':
q.page['example'].value = templates['template_poem']
elif q.args.dropdown == 'template_summarize':
q.page['example'].value = templates['template_summarize']
elif q.args.dropdown == 'template_professional':
q.page['example'].value = templates['template_professional']
# A new message arrived.
elif q.args.chatbot:
# Append user message.
q.page['example'].data += [q.args.chatbot, True]
# Append bot response.
q.page['example'].data += ['I am a fake chatbot. Sorry, I cannot help you.', False]

await q.page.save()
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ chatbot_events_stop.py
chatbot_events_scroll.py
chatbot_events_feedback.py
chatbot_events_suggestions.py
chatbot_value.py
form.py
form_visibility.py
text.py
Expand Down
10 changes: 10 additions & 0 deletions py/h2o_lightwave/h2o_lightwave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8467,6 +8467,7 @@ def __init__(
generating: Optional[bool] = None,
suggestions: Optional[List[ChatSuggestion]] = None,
disabled: Optional[bool] = None,
value: Optional[str] = None,
commands: Optional[List[Command]] = None,
):
_guard_scalar('ChatbotCard.box', box, (str,), False, False, False)
Expand All @@ -8476,6 +8477,7 @@ def __init__(
_guard_scalar('ChatbotCard.generating', generating, (bool,), False, True, False)
_guard_vector('ChatbotCard.suggestions', suggestions, (ChatSuggestion,), False, True, False)
_guard_scalar('ChatbotCard.disabled', disabled, (bool,), False, True, False)
_guard_scalar('ChatbotCard.value', value, (str,), False, True, False)
_guard_vector('ChatbotCard.commands', commands, (Command,), False, True, False)
self.box = box
"""A string indicating how to place this component on the page."""
Expand All @@ -8493,6 +8495,8 @@ def __init__(
"""Clickable prompt suggestions shown below the last response."""
self.disabled = disabled
"""True if the user input should be disabled."""
self.value = value
"""Value of the user input."""
self.commands = commands
"""Contextual menu commands for this component."""

Expand All @@ -8505,6 +8509,7 @@ def dump(self) -> Dict:
_guard_scalar('ChatbotCard.generating', self.generating, (bool,), False, True, False)
_guard_vector('ChatbotCard.suggestions', self.suggestions, (ChatSuggestion,), False, True, False)
_guard_scalar('ChatbotCard.disabled', self.disabled, (bool,), False, True, False)
_guard_scalar('ChatbotCard.value', self.value, (str,), False, True, False)
_guard_vector('ChatbotCard.commands', self.commands, (Command,), False, True, False)
return _dump(
view='chatbot',
Expand All @@ -8516,6 +8521,7 @@ def dump(self) -> Dict:
generating=self.generating,
suggestions=None if self.suggestions is None else [__e.dump() for __e in self.suggestions],
disabled=self.disabled,
value=self.value,
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
)

Expand All @@ -8537,6 +8543,8 @@ def load(__d: Dict) -> 'ChatbotCard':
_guard_vector('ChatbotCard.suggestions', __d_suggestions, (dict,), False, True, False)
__d_disabled: Any = __d.get('disabled')
_guard_scalar('ChatbotCard.disabled', __d_disabled, (bool,), False, True, False)
__d_value: Any = __d.get('value')
_guard_scalar('ChatbotCard.value', __d_value, (str,), False, True, False)
__d_commands: Any = __d.get('commands')
_guard_vector('ChatbotCard.commands', __d_commands, (dict,), False, True, False)
box: str = __d_box
Expand All @@ -8547,6 +8555,7 @@ def load(__d: Dict) -> 'ChatbotCard':
generating: Optional[bool] = __d_generating
suggestions: Optional[List[ChatSuggestion]] = None if __d_suggestions is None else [ChatSuggestion.load(__e) for __e in __d_suggestions]
disabled: Optional[bool] = __d_disabled
value: Optional[str] = __d_value
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
return ChatbotCard(
box,
Expand All @@ -8557,6 +8566,7 @@ def load(__d: Dict) -> 'ChatbotCard':
generating,
suggestions,
disabled,
value,
commands,
)

Expand Down
3 changes: 3 additions & 0 deletions py/h2o_lightwave/h2o_lightwave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2938,6 +2938,7 @@ def chatbot_card(
generating: Optional[bool] = None,
suggestions: Optional[List[ChatSuggestion]] = None,
disabled: Optional[bool] = None,
value: Optional[str] = None,
commands: Optional[List[Command]] = None,
) -> ChatbotCard:
"""Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers.
Expand All @@ -2951,6 +2952,7 @@ def chatbot_card(
generating: True to show a button to stop the text generation. Defaults to False.
suggestions: Clickable prompt suggestions shown below the last response.
disabled: True if the user input should be disabled.
value: Value of the user input.
commands: Contextual menu commands for this component.
Returns:
A `h2o_wave.types.ChatbotCard` instance.
Expand All @@ -2964,6 +2966,7 @@ def chatbot_card(
generating,
suggestions,
disabled,
value,
commands,
)

Expand Down
10 changes: 10 additions & 0 deletions py/h2o_wave/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8467,6 +8467,7 @@ def __init__(
generating: Optional[bool] = None,
suggestions: Optional[List[ChatSuggestion]] = None,
disabled: Optional[bool] = None,
value: Optional[str] = None,
commands: Optional[List[Command]] = None,
):
_guard_scalar('ChatbotCard.box', box, (str,), False, False, False)
Expand All @@ -8476,6 +8477,7 @@ def __init__(
_guard_scalar('ChatbotCard.generating', generating, (bool,), False, True, False)
_guard_vector('ChatbotCard.suggestions', suggestions, (ChatSuggestion,), False, True, False)
_guard_scalar('ChatbotCard.disabled', disabled, (bool,), False, True, False)
_guard_scalar('ChatbotCard.value', value, (str,), False, True, False)
_guard_vector('ChatbotCard.commands', commands, (Command,), False, True, False)
self.box = box
"""A string indicating how to place this component on the page."""
Expand All @@ -8493,6 +8495,8 @@ def __init__(
"""Clickable prompt suggestions shown below the last response."""
self.disabled = disabled
"""True if the user input should be disabled."""
self.value = value
"""Value of the user input."""
self.commands = commands
"""Contextual menu commands for this component."""

Expand All @@ -8505,6 +8509,7 @@ def dump(self) -> Dict:
_guard_scalar('ChatbotCard.generating', self.generating, (bool,), False, True, False)
_guard_vector('ChatbotCard.suggestions', self.suggestions, (ChatSuggestion,), False, True, False)
_guard_scalar('ChatbotCard.disabled', self.disabled, (bool,), False, True, False)
_guard_scalar('ChatbotCard.value', self.value, (str,), False, True, False)
_guard_vector('ChatbotCard.commands', self.commands, (Command,), False, True, False)
return _dump(
view='chatbot',
Expand All @@ -8516,6 +8521,7 @@ def dump(self) -> Dict:
generating=self.generating,
suggestions=None if self.suggestions is None else [__e.dump() for __e in self.suggestions],
disabled=self.disabled,
value=self.value,
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
)

Expand All @@ -8537,6 +8543,8 @@ def load(__d: Dict) -> 'ChatbotCard':
_guard_vector('ChatbotCard.suggestions', __d_suggestions, (dict,), False, True, False)
__d_disabled: Any = __d.get('disabled')
_guard_scalar('ChatbotCard.disabled', __d_disabled, (bool,), False, True, False)
__d_value: Any = __d.get('value')
_guard_scalar('ChatbotCard.value', __d_value, (str,), False, True, False)
__d_commands: Any = __d.get('commands')
_guard_vector('ChatbotCard.commands', __d_commands, (dict,), False, True, False)
box: str = __d_box
Expand All @@ -8547,6 +8555,7 @@ def load(__d: Dict) -> 'ChatbotCard':
generating: Optional[bool] = __d_generating
suggestions: Optional[List[ChatSuggestion]] = None if __d_suggestions is None else [ChatSuggestion.load(__e) for __e in __d_suggestions]
disabled: Optional[bool] = __d_disabled
value: Optional[str] = __d_value
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
return ChatbotCard(
box,
Expand All @@ -8557,6 +8566,7 @@ def load(__d: Dict) -> 'ChatbotCard':
generating,
suggestions,
disabled,
value,
commands,
)

Expand Down
3 changes: 3 additions & 0 deletions py/h2o_wave/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2938,6 +2938,7 @@ def chatbot_card(
generating: Optional[bool] = None,
suggestions: Optional[List[ChatSuggestion]] = None,
disabled: Optional[bool] = None,
value: Optional[str] = None,
commands: Optional[List[Command]] = None,
) -> ChatbotCard:
"""Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers.
Expand All @@ -2951,6 +2952,7 @@ def chatbot_card(
generating: True to show a button to stop the text generation. Defaults to False.
suggestions: Clickable prompt suggestions shown below the last response.
disabled: True if the user input should be disabled.
value: Value of the user input.
commands: Contextual menu commands for this component.
Returns:
A `h2o_wave.types.ChatbotCard` instance.
Expand All @@ -2964,6 +2966,7 @@ def chatbot_card(
generating,
suggestions,
disabled,
value,
commands,
)

Expand Down
4 changes: 4 additions & 0 deletions r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -3417,6 +3417,7 @@ ui_chat_suggestion <- function(
#' @param generating True to show a button to stop the text generation. Defaults to False.
#' @param suggestions Clickable prompt suggestions shown below the last response.
#' @param disabled True if the user input should be disabled.
#' @param value Value of the user input.
#' @param commands Contextual menu commands for this component.
#' @return A ChatbotCard instance.
#' @export
Expand All @@ -3429,6 +3430,7 @@ ui_chatbot_card <- function(
generating = NULL,
suggestions = NULL,
disabled = NULL,
value = NULL,
commands = NULL) {
.guard_scalar("box", "character", box)
.guard_scalar("name", "character", name)
Expand All @@ -3438,6 +3440,7 @@ ui_chatbot_card <- function(
.guard_scalar("generating", "logical", generating)
.guard_vector("suggestions", "WaveChatSuggestion", suggestions)
.guard_scalar("disabled", "logical", disabled)
.guard_scalar("value", "character", value)
.guard_vector("commands", "WaveCommand", commands)
.o <- list(
box=box,
Expand All @@ -3448,6 +3451,7 @@ ui_chatbot_card <- function(
generating=generating,
suggestions=suggestions,
disabled=disabled,
value=value,
commands=commands,
view='chatbot')
class(.o) <- append(class(.o), c(.wave_obj, "WaveChatbotCard"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1138,26 +1138,28 @@
<option name="Python" value="true"/>
</context>
</template>
<template name="w_full_chatbot" value="ui.chatbot(name='$name$',data=$data$,placeholder='$placeholder$',generating=$generating$,disabled=$disabled$,events=[&#10; $events$ &#10;],prev_items=[&#10; $prev_items$ &#10;],suggestions=[&#10; $suggestions$ &#10;]),$END$" description="Create Wave Chatbot with full attributes." toReformat="true" toShortenFQNames="true">
<template name="w_full_chatbot" value="ui.chatbot(name='$name$',data=$data$,placeholder='$placeholder$',generating=$generating$,disabled=$disabled$,value='$value$',events=[&#10; $events$ &#10;],prev_items=[&#10; $prev_items$ &#10;],suggestions=[&#10; $suggestions$ &#10;]),$END$" description="Create Wave Chatbot with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="name" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="data" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="placeholder" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="generating" expression="" defaultValue="&quot;False&quot;" alwaysStopAt="true"/>
<variable name="disabled" expression="" defaultValue="&quot;False&quot;" alwaysStopAt="true"/>
<variable name="value" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="events" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="prev_items" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="suggestions" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
<option name="Python" value="true"/>
</context>
</template>
<template name="w_full_chatbot_card" value="ui.chatbot_card(box='$box$',name='$name$',data=$data$,placeholder='$placeholder$',generating=$generating$,disabled=$disabled$,events=[&#10; $events$ &#10;],suggestions=[&#10; $suggestions$ &#10;],commands=[&#10; $commands$ &#10;])$END$" description="Create Wave ChatbotCard with full attributes." toReformat="true" toShortenFQNames="true">
<template name="w_full_chatbot_card" value="ui.chatbot_card(box='$box$',name='$name$',data=$data$,placeholder='$placeholder$',generating=$generating$,disabled=$disabled$,value='$value$',events=[&#10; $events$ &#10;],suggestions=[&#10; $suggestions$ &#10;],commands=[&#10; $commands$ &#10;])$END$" description="Create Wave ChatbotCard with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="box" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="name" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="data" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="placeholder" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="generating" expression="" defaultValue="&quot;False&quot;" alwaysStopAt="true"/>
<variable name="disabled" expression="" defaultValue="&quot;False&quot;" alwaysStopAt="true"/>
<variable name="value" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="events" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="suggestions" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="commands" expression="" defaultValue="" alwaysStopAt="true"/>
Expand Down
4 changes: 2 additions & 2 deletions tools/vscode-extension/component-snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,14 +1073,14 @@
"Wave Full Chatbot": {
"prefix": "w_full_chatbot",
"body": [
"ui.chatbot(name='$1', data=$2, placeholder='$3', generating=${4:False}, disabled=${5:False}, events=[\n\t\t$6\t\t\n], prev_items=[\n\t\t$7\t\t\n], suggestions=[\n\t\t$8\t\t\n]),$0"
"ui.chatbot(name='$1', data=$2, placeholder='$3', generating=${4:False}, disabled=${5:False}, value='$6', events=[\n\t\t$7\t\t\n], prev_items=[\n\t\t$8\t\t\n], suggestions=[\n\t\t$9\t\t\n]),$0"
],
"description": "Create a full Wave Chatbot."
},
"Wave Full ChatbotCard": {
"prefix": "w_full_chatbot_card",
"body": [
"ui.chatbot_card(box='$1', name='$2', data=$3, placeholder='$4', generating=${5:False}, disabled=${6:False}, events=[\n\t\t$7\t\t\n], suggestions=[\n\t\t$8\t\t\n], commands=[\n\t\t$9\t\t\n])$0"
"ui.chatbot_card(box='$1', name='$2', data=$3, placeholder='$4', generating=${5:False}, disabled=${6:False}, value='$7', events=[\n\t\t$8\t\t\n], suggestions=[\n\t\t$9\t\t\n], commands=[\n\t\t$10\t\t\n])$0"
],
"description": "Create a full Wave ChatbotCard."
},
Expand Down
21 changes: 21 additions & 0 deletions ui/src/chatbot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,25 @@ describe('XChatbot', () => {
expect(emitMock).toHaveBeenCalledTimes(2)
expect(emitMock).toHaveBeenCalledWith(model.name, 'suggestion', suggestions[1].name)
})

it('Has input with default value', () => {
const { getByRole } = render(<XChatbot {...{ ...model, value: 'Initial input' }} />)
const input = getByRole('textbox')

expect(input).toHaveValue('Initial input')
expect(wave.args[name]).toBe('Initial input')
})

it('Change user input dynamically', () => {
const { getByRole, rerender } = render(<XChatbot {...model} />)
const input = getByRole('textbox')

expect(wave.args[name]).toBe(null)
expect(input).toHaveValue('')

rerender(<XChatbot {...{ ...model, value: 'Hello!' }} />)

expect(input).toHaveValue('Hello!')
expect(wave.args[name]).toBe('Hello!')
})
})
8 changes: 8 additions & 0 deletions ui/src/chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export interface Chatbot {
suggestions?: ChatSuggestion[]
/** True if the user input should be disabled. */
disabled?: B
/** Value of the user input. */
value?: S
}

const processData = (data: Rec) => unpack<ChatbotMessage[]>(data).map(({ content, from_user }) => ({ content, from_user }))
Expand Down Expand Up @@ -233,6 +235,10 @@ export const XChatbot = (props: Chatbot) => {
? msgContainerRef.current.scrollTo({ top: topOffset, behavior: 'smooth' })
: msgContainerRef.current.scrollTop = topOffset
}, [msgs])
React.useEffect(() => {
wave.args[props.name] = props.value ?? null
setUserInput(props.value ?? '')
}, [props.value, props.name])

return (
<div className={css.chatWindow}>
Expand Down Expand Up @@ -341,6 +347,8 @@ interface State {
suggestions?: ChatSuggestion[]
/** True if the user input should be disabled. */
disabled?: B
/** Value of the user input. */
value?: S
}

export const View = bond(({ name, state, changed }: Model<State>) => {
Expand Down
Binary file added website/docs/examples/assets/chatbot-value.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d6ceb3b

Please sign in to comment.