Skip to content

Commit

Permalink
feat: Prompt suggestions for ui.chatbot #2250 (#2265)
Browse files Browse the repository at this point in the history
  • Loading branch information
marek-mihok authored Feb 16, 2024
1 parent fc273d9 commit 15283bf
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 14 deletions.
62 changes: 62 additions & 0 deletions py/examples/chatbot_events_suggestions.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 80 additions & 1 deletion py/h2o_lightwave/h2o_lightwave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8265,6 +8265,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.
"""
Expand All @@ -8276,13 +8335,17 @@ 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)
_guard_scalar('ChatbotCard.name', name, (str,), True, False, False)
_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."""
Expand All @@ -8293,9 +8356,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."""

Expand All @@ -8306,6 +8373,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',
Expand All @@ -8315,6 +8384,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],
)

Expand All @@ -8332,6 +8403,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
Expand All @@ -8340,6 +8415,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,
Expand All @@ -8348,6 +8425,8 @@ def load(__d: Dict) -> 'ChatbotCard':
placeholder,
events,
generating,
suggestions,
disabled,
commands,
)

Expand Down
32 changes: 31 additions & 1 deletion py/h2o_lightwave/h2o_lightwave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2884,13 +2884,39 @@ 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,
data: PackedRecord,
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.
Expand All @@ -2900,8 +2926,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.
Expand All @@ -2913,6 +2941,8 @@ def chatbot_card(
placeholder,
events,
generating,
suggestions,
disabled,
commands,
)

Expand Down
Loading

0 comments on commit 15283bf

Please sign in to comment.