Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Prompt suggestions for ui.chatbot #2250 #2265

Merged
merged 16 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -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.
"""
Expand All @@ -8256,13 +8315,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 @@ -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."""

Expand All @@ -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',
Expand All @@ -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],
)

Expand All @@ -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
Expand All @@ -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,
Expand All @@ -8328,6 +8405,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 @@ -2878,13 +2878,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 @@ -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.
Expand All @@ -2907,6 +2935,8 @@ def chatbot_card(
placeholder,
events,
generating,
suggestions,
disabled,
commands,
)

Expand Down
Loading
Loading