diff --git a/py/examples/chatbot_events_suggestions.py b/py/examples/chatbot_events_suggestions.py new file mode 100644 index 0000000000..12d2891eb5 --- /dev/null +++ b/py/examples/chatbot_events_suggestions.py @@ -0,0 +1,62 @@ +# Chatbot / Events/ Suggestions +# Use suggestions to simplify user interaction. +# #chatbot #events #suggestions +# --- +from h2o_wave import main, app, Q, ui, data + +# Dictionary containing label, caption and icon for each suggestion. +suggestions = { + 'sug1': ["Write a poem", "about H2O Wave", "Edit"], + 'sug2': ["Plan a trip", "to Europe", "Airplane"], + 'sug3': ["Give me ideas", "for a new project", "Lightbulb"], + 'sug4': ["Explain me", "CSS preprocessors", "Code"] +} + + +async def stream_bot_response(q: Q, message: str): + stream = '' + # Fake bot "thinking" time. + await q.sleep(0.5) + # Stream bot response. + for w in 'I am a fake chatbot. Sorry, I cannot help you.'.split(): + await q.sleep(0.1) + stream += w + ' ' + q.page['example'].data[-1] = [stream, False] + await q.page.save() + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.chatbot_card( + box='1 1 5 5', + data=data(fields='content from_user', t='list'), + name='chatbot', + placeholder='Ask me anything...', + events=['suggestion'], + suggestions=[ui.chat_suggestion(name, label=value[0], caption=value[1], icon=value[2]) for + name, value in suggestions.items()] + ) + q.client.initialized = True + + elif q.events.chatbot or q.args.chatbot: + # Clear suggestions. + q.page['example'].suggestions = [] + + # Handle user input. + if q.args.chatbot: + # Append user message typed manually. + q.page['example'].data += [q.args.chatbot, True] + else: + label, caption, icon = suggestions[q.events.chatbot.suggestion] + # Append user message based on the suggestion event. + q.page['example'].data += [label + ' ' + caption, True] + + # Append bot response. + q.page['example'].data += ['', False] + # Update UI. + await q.page.save() + # Stream bot response. + await stream_bot_response(q, 'I am a fake chatbot. Sorry, I cannot help you.') + + await q.page.save() diff --git a/py/examples/tour.conf b/py/examples/tour.conf index a0bfc3992b..557b090506 100644 --- a/py/examples/tour.conf +++ b/py/examples/tour.conf @@ -36,6 +36,7 @@ chatbot_stream.py chatbot_events_stop.py chatbot_events_scroll.py chatbot_events_feedback.py +chatbot_events_suggestions.py form.py form_visibility.py text.py diff --git a/py/h2o_lightwave/h2o_lightwave/types.py b/py/h2o_lightwave/h2o_lightwave/types.py index 8887cc6b1e..7638d27402 100644 --- a/py/h2o_lightwave/h2o_lightwave/types.py +++ b/py/h2o_lightwave/h2o_lightwave/types.py @@ -8245,6 +8245,65 @@ def load(__d: Dict) -> 'ChatCard': ) +class ChatSuggestion: + """Create a chat prompt suggestion displayed as button below the last response in the chatbot component. + """ + def __init__( + self, + name: str, + label: str, + caption: Optional[str] = None, + icon: Optional[str] = None, + ): + _guard_scalar('ChatSuggestion.name', name, (str,), True, False, False) + _guard_scalar('ChatSuggestion.label', label, (str,), False, False, False) + _guard_scalar('ChatSuggestion.caption', caption, (str,), False, True, False) + _guard_scalar('ChatSuggestion.icon', icon, (str,), False, True, False) + self.name = name + """An identifying name for this component.""" + self.label = label + """The text displayed for this suggestion.""" + self.caption = caption + """The caption displayed below the label.""" + self.icon = icon + """The icon to be displayed for this suggestion.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('ChatSuggestion.name', self.name, (str,), True, False, False) + _guard_scalar('ChatSuggestion.label', self.label, (str,), False, False, False) + _guard_scalar('ChatSuggestion.caption', self.caption, (str,), False, True, False) + _guard_scalar('ChatSuggestion.icon', self.icon, (str,), False, True, False) + return _dump( + name=self.name, + label=self.label, + caption=self.caption, + icon=self.icon, + ) + + @staticmethod + def load(__d: Dict) -> 'ChatSuggestion': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('ChatSuggestion.name', __d_name, (str,), True, False, False) + __d_label: Any = __d.get('label') + _guard_scalar('ChatSuggestion.label', __d_label, (str,), False, False, False) + __d_caption: Any = __d.get('caption') + _guard_scalar('ChatSuggestion.caption', __d_caption, (str,), False, True, False) + __d_icon: Any = __d.get('icon') + _guard_scalar('ChatSuggestion.icon', __d_icon, (str,), False, True, False) + name: str = __d_name + label: str = __d_label + caption: Optional[str] = __d_caption + icon: Optional[str] = __d_icon + return ChatSuggestion( + name, + label, + caption, + icon, + ) + + class ChatbotCard: """Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers. """ @@ -8256,6 +8315,8 @@ def __init__( placeholder: Optional[str] = None, events: Optional[List[str]] = None, generating: Optional[bool] = None, + suggestions: Optional[List[ChatSuggestion]] = None, + disabled: Optional[bool] = None, commands: Optional[List[Command]] = None, ): _guard_scalar('ChatbotCard.box', box, (str,), False, False, False) @@ -8263,6 +8324,8 @@ def __init__( _guard_scalar('ChatbotCard.placeholder', placeholder, (str,), False, True, False) _guard_vector('ChatbotCard.events', events, (str,), False, True, False) _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_vector('ChatbotCard.commands', commands, (Command,), False, True, False) self.box = box """A string indicating how to place this component on the page.""" @@ -8273,9 +8336,13 @@ def __init__( self.placeholder = placeholder """Chat input box placeholder. Use for prompt examples.""" self.events = events - """The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback'.""" + """The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback' | 'suggestion'.""" self.generating = generating """True to show a button to stop the text generation. Defaults to False.""" + self.suggestions = suggestions + """Clickable prompt suggestions shown below the last response.""" + self.disabled = disabled + """True if the user input should be disabled.""" self.commands = commands """Contextual menu commands for this component.""" @@ -8286,6 +8353,8 @@ def dump(self) -> Dict: _guard_scalar('ChatbotCard.placeholder', self.placeholder, (str,), False, True, False) _guard_vector('ChatbotCard.events', self.events, (str,), False, True, False) _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_vector('ChatbotCard.commands', self.commands, (Command,), False, True, False) return _dump( view='chatbot', @@ -8295,6 +8364,8 @@ def dump(self) -> Dict: placeholder=self.placeholder, events=self.events, generating=self.generating, + suggestions=None if self.suggestions is None else [__e.dump() for __e in self.suggestions], + disabled=self.disabled, commands=None if self.commands is None else [__e.dump() for __e in self.commands], ) @@ -8312,6 +8383,10 @@ def load(__d: Dict) -> 'ChatbotCard': _guard_vector('ChatbotCard.events', __d_events, (str,), False, True, False) __d_generating: Any = __d.get('generating') _guard_scalar('ChatbotCard.generating', __d_generating, (bool,), False, True, False) + __d_suggestions: Any = __d.get('suggestions') + _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_commands: Any = __d.get('commands') _guard_vector('ChatbotCard.commands', __d_commands, (dict,), False, True, False) box: str = __d_box @@ -8320,6 +8395,8 @@ def load(__d: Dict) -> 'ChatbotCard': placeholder: Optional[str] = __d_placeholder events: Optional[List[str]] = __d_events 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 commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands] return ChatbotCard( box, @@ -8328,6 +8405,8 @@ def load(__d: Dict) -> 'ChatbotCard': placeholder, events, generating, + suggestions, + disabled, commands, ) diff --git a/py/h2o_lightwave/h2o_lightwave/ui.py b/py/h2o_lightwave/h2o_lightwave/ui.py index 9967255f1d..3e0d8fa422 100644 --- a/py/h2o_lightwave/h2o_lightwave/ui.py +++ b/py/h2o_lightwave/h2o_lightwave/ui.py @@ -2878,6 +2878,30 @@ def chat_card( ) +def chat_suggestion( + name: str, + label: str, + caption: Optional[str] = None, + icon: Optional[str] = None, +) -> ChatSuggestion: + """Create a chat prompt suggestion displayed as button below the last response in the chatbot component. + + Args: + name: An identifying name for this component. + label: The text displayed for this suggestion. + caption: The caption displayed below the label. + icon: The icon to be displayed for this suggestion. + Returns: + A `h2o_wave.types.ChatSuggestion` instance. + """ + return ChatSuggestion( + name, + label, + caption, + icon, + ) + + def chatbot_card( box: str, name: str, @@ -2885,6 +2909,8 @@ def chatbot_card( placeholder: Optional[str] = None, events: Optional[List[str]] = None, generating: Optional[bool] = None, + suggestions: Optional[List[ChatSuggestion]] = None, + disabled: Optional[bool] = None, commands: Optional[List[Command]] = None, ) -> ChatbotCard: """Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers. @@ -2894,8 +2920,10 @@ def chatbot_card( name: An identifying name for this component. data: Chat messages data. Requires cyclic buffer. placeholder: Chat input box placeholder. Use for prompt examples. - events: The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback'. + events: The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback' | 'suggestion'. 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. commands: Contextual menu commands for this component. Returns: A `h2o_wave.types.ChatbotCard` instance. @@ -2907,6 +2935,8 @@ def chatbot_card( placeholder, events, generating, + suggestions, + disabled, commands, ) diff --git a/py/h2o_wave/h2o_wave/types.py b/py/h2o_wave/h2o_wave/types.py index 8887cc6b1e..7638d27402 100644 --- a/py/h2o_wave/h2o_wave/types.py +++ b/py/h2o_wave/h2o_wave/types.py @@ -8245,6 +8245,65 @@ def load(__d: Dict) -> 'ChatCard': ) +class ChatSuggestion: + """Create a chat prompt suggestion displayed as button below the last response in the chatbot component. + """ + def __init__( + self, + name: str, + label: str, + caption: Optional[str] = None, + icon: Optional[str] = None, + ): + _guard_scalar('ChatSuggestion.name', name, (str,), True, False, False) + _guard_scalar('ChatSuggestion.label', label, (str,), False, False, False) + _guard_scalar('ChatSuggestion.caption', caption, (str,), False, True, False) + _guard_scalar('ChatSuggestion.icon', icon, (str,), False, True, False) + self.name = name + """An identifying name for this component.""" + self.label = label + """The text displayed for this suggestion.""" + self.caption = caption + """The caption displayed below the label.""" + self.icon = icon + """The icon to be displayed for this suggestion.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('ChatSuggestion.name', self.name, (str,), True, False, False) + _guard_scalar('ChatSuggestion.label', self.label, (str,), False, False, False) + _guard_scalar('ChatSuggestion.caption', self.caption, (str,), False, True, False) + _guard_scalar('ChatSuggestion.icon', self.icon, (str,), False, True, False) + return _dump( + name=self.name, + label=self.label, + caption=self.caption, + icon=self.icon, + ) + + @staticmethod + def load(__d: Dict) -> 'ChatSuggestion': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('ChatSuggestion.name', __d_name, (str,), True, False, False) + __d_label: Any = __d.get('label') + _guard_scalar('ChatSuggestion.label', __d_label, (str,), False, False, False) + __d_caption: Any = __d.get('caption') + _guard_scalar('ChatSuggestion.caption', __d_caption, (str,), False, True, False) + __d_icon: Any = __d.get('icon') + _guard_scalar('ChatSuggestion.icon', __d_icon, (str,), False, True, False) + name: str = __d_name + label: str = __d_label + caption: Optional[str] = __d_caption + icon: Optional[str] = __d_icon + return ChatSuggestion( + name, + label, + caption, + icon, + ) + + class ChatbotCard: """Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers. """ @@ -8256,6 +8315,8 @@ def __init__( placeholder: Optional[str] = None, events: Optional[List[str]] = None, generating: Optional[bool] = None, + suggestions: Optional[List[ChatSuggestion]] = None, + disabled: Optional[bool] = None, commands: Optional[List[Command]] = None, ): _guard_scalar('ChatbotCard.box', box, (str,), False, False, False) @@ -8263,6 +8324,8 @@ def __init__( _guard_scalar('ChatbotCard.placeholder', placeholder, (str,), False, True, False) _guard_vector('ChatbotCard.events', events, (str,), False, True, False) _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_vector('ChatbotCard.commands', commands, (Command,), False, True, False) self.box = box """A string indicating how to place this component on the page.""" @@ -8273,9 +8336,13 @@ def __init__( self.placeholder = placeholder """Chat input box placeholder. Use for prompt examples.""" self.events = events - """The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback'.""" + """The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback' | 'suggestion'.""" self.generating = generating """True to show a button to stop the text generation. Defaults to False.""" + self.suggestions = suggestions + """Clickable prompt suggestions shown below the last response.""" + self.disabled = disabled + """True if the user input should be disabled.""" self.commands = commands """Contextual menu commands for this component.""" @@ -8286,6 +8353,8 @@ def dump(self) -> Dict: _guard_scalar('ChatbotCard.placeholder', self.placeholder, (str,), False, True, False) _guard_vector('ChatbotCard.events', self.events, (str,), False, True, False) _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_vector('ChatbotCard.commands', self.commands, (Command,), False, True, False) return _dump( view='chatbot', @@ -8295,6 +8364,8 @@ def dump(self) -> Dict: placeholder=self.placeholder, events=self.events, generating=self.generating, + suggestions=None if self.suggestions is None else [__e.dump() for __e in self.suggestions], + disabled=self.disabled, commands=None if self.commands is None else [__e.dump() for __e in self.commands], ) @@ -8312,6 +8383,10 @@ def load(__d: Dict) -> 'ChatbotCard': _guard_vector('ChatbotCard.events', __d_events, (str,), False, True, False) __d_generating: Any = __d.get('generating') _guard_scalar('ChatbotCard.generating', __d_generating, (bool,), False, True, False) + __d_suggestions: Any = __d.get('suggestions') + _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_commands: Any = __d.get('commands') _guard_vector('ChatbotCard.commands', __d_commands, (dict,), False, True, False) box: str = __d_box @@ -8320,6 +8395,8 @@ def load(__d: Dict) -> 'ChatbotCard': placeholder: Optional[str] = __d_placeholder events: Optional[List[str]] = __d_events 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 commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands] return ChatbotCard( box, @@ -8328,6 +8405,8 @@ def load(__d: Dict) -> 'ChatbotCard': placeholder, events, generating, + suggestions, + disabled, commands, ) diff --git a/py/h2o_wave/h2o_wave/ui.py b/py/h2o_wave/h2o_wave/ui.py index 9967255f1d..3e0d8fa422 100644 --- a/py/h2o_wave/h2o_wave/ui.py +++ b/py/h2o_wave/h2o_wave/ui.py @@ -2878,6 +2878,30 @@ def chat_card( ) +def chat_suggestion( + name: str, + label: str, + caption: Optional[str] = None, + icon: Optional[str] = None, +) -> ChatSuggestion: + """Create a chat prompt suggestion displayed as button below the last response in the chatbot component. + + Args: + name: An identifying name for this component. + label: The text displayed for this suggestion. + caption: The caption displayed below the label. + icon: The icon to be displayed for this suggestion. + Returns: + A `h2o_wave.types.ChatSuggestion` instance. + """ + return ChatSuggestion( + name, + label, + caption, + icon, + ) + + def chatbot_card( box: str, name: str, @@ -2885,6 +2909,8 @@ def chatbot_card( placeholder: Optional[str] = None, events: Optional[List[str]] = None, generating: Optional[bool] = None, + suggestions: Optional[List[ChatSuggestion]] = None, + disabled: Optional[bool] = None, commands: Optional[List[Command]] = None, ) -> ChatbotCard: """Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers. @@ -2894,8 +2920,10 @@ def chatbot_card( name: An identifying name for this component. data: Chat messages data. Requires cyclic buffer. placeholder: Chat input box placeholder. Use for prompt examples. - events: The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback'. + events: The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback' | 'suggestion'. 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. commands: Contextual menu commands for this component. Returns: A `h2o_wave.types.ChatbotCard` instance. @@ -2907,6 +2935,8 @@ def chatbot_card( placeholder, events, generating, + suggestions, + disabled, commands, ) diff --git a/r/R/ui.R b/r/R/ui.R index 4f603b4053..ceed561880 100644 --- a/r/R/ui.R +++ b/r/R/ui.R @@ -3339,14 +3339,42 @@ ui_chat_card <- function( return(.o) } +#' Create a chat prompt suggestion displayed as button below the last response in the chatbot component. +#' +#' @param name An identifying name for this component. +#' @param label The text displayed for this suggestion. +#' @param caption The caption displayed below the label. +#' @param icon The icon to be displayed for this suggestion. +#' @return A ChatSuggestion instance. +#' @export +ui_chat_suggestion <- function( + name, + label, + caption = NULL, + icon = NULL) { + .guard_scalar("name", "character", name) + .guard_scalar("label", "character", label) + .guard_scalar("caption", "character", caption) + .guard_scalar("icon", "character", icon) + .o <- list( + name=name, + label=label, + caption=caption, + icon=icon) + class(.o) <- append(class(.o), c(.wave_obj, "WaveChatSuggestion")) + return(.o) +} + #' Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers. #' #' @param box A string indicating how to place this component on the page. #' @param name An identifying name for this component. #' @param data Chat messages data. Requires cyclic buffer. #' @param placeholder Chat input box placeholder. Use for prompt examples. -#' @param events The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback'. +#' @param events The events to capture on this chatbot. One of 'stop' | 'scroll_up' | 'feedback' | 'suggestion'. #' @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 commands Contextual menu commands for this component. #' @return A ChatbotCard instance. #' @export @@ -3357,6 +3385,8 @@ ui_chatbot_card <- function( placeholder = NULL, events = NULL, generating = NULL, + suggestions = NULL, + disabled = NULL, commands = NULL) { .guard_scalar("box", "character", box) .guard_scalar("name", "character", name) @@ -3364,6 +3394,8 @@ ui_chatbot_card <- function( .guard_scalar("placeholder", "character", placeholder) .guard_vector("events", "character", events) .guard_scalar("generating", "logical", generating) + .guard_vector("suggestions", "WaveChatSuggestion", suggestions) + .guard_scalar("disabled", "logical", disabled) .guard_vector("commands", "WaveCommand", commands) .o <- list( box=box, @@ -3372,6 +3404,8 @@ ui_chatbot_card <- function( placeholder=placeholder, events=events, generating=generating, + suggestions=suggestions, + disabled=disabled, commands=commands, view='chatbot') class(.o) <- append(class(.o), c(.wave_obj, "WaveChatbotCard")) diff --git a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml index ae064bd8ab..732d93a6f2 100644 --- a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml +++ b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml @@ -95,6 +95,13 @@