Skip to content

Commit

Permalink
Mesop UI improvements (#241)
Browse files Browse the repository at this point in the history
* scroll to the start of conversation for past ones

* in past chats box use shortened conversation title

* put user input on the left, responses to the right

* user input to the right, autogen to the left

* Bump Mesop to 0.12.4

* enter completes input wip(1)

* enter completes input

* togetherai list of models updated

* packages updated

* Semgrep rule ignore added

---------

Co-authored-by: Davor Runje <[email protected]>
  • Loading branch information
davorinrusevljan and davorrunje authored Sep 23, 2024
1 parent 845c21e commit f42290e
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 74 deletions.
3 changes: 1 addition & 2 deletions docs/docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,7 @@ search:
- [run_workflow](api/fastagency/ui/mesop/base/run_workflow.md)
- components
- inputs
- [input_prompt](api/fastagency/ui/mesop/components/inputs/input_prompt.md)
- [input_user_feedback](api/fastagency/ui/mesop/components/inputs/input_user_feedback.md)
- [input_text](api/fastagency/ui/mesop/components/inputs/input_text.md)
- ui_common
- [darken_hex_color](api/fastagency/ui/mesop/components/ui_common/darken_hex_color.md)
- [header](api/fastagency/ui/mesop/components/ui_common/header.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ search:
boost: 0.5
---

::: fastagency.ui.mesop.components.inputs.input_prompt
::: fastagency.ui.mesop.components.inputs.input_text

This file was deleted.

101 changes: 61 additions & 40 deletions fastagency/ui/mesop/components/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,53 @@
from ..data_model import State


def _on_blur(e: me.InputBlurEvent) -> None:
state = me.state(State)
setattr(state, e.key, e.value)


def input_user_feedback(
send_feedback: Callable[[me.ClickEvent], Iterator[None]],
def input_text( # noqa: C901
on_input: Callable[[str], Iterator[None]],
key: str,
disabled: bool = False,
value: Optional[str] = None,
) -> None:
def _on_feedback_blur(e: me.InputBlurEvent) -> None:
def on_click(e: me.ClickEvent) -> Iterator[None]:
state = me.state(State)
output_key = get_output_key()
inp = getattr(state, output_key)
clear_in_out()
yield from on_input(inp)

def on_newline(e: me.TextareaShortcutEvent) -> Iterator[None]:
state = me.state(State)
state.conversation.feedback = e.value
input_key = get_input_key()
setattr(state, input_key, e.value + "\n")
yield

def on_submit(e: me.TextareaShortcutEvent) -> Iterator[None]:
clear_in_out()
yield from on_input(e.value)

def on_blur(e: me.InputBlurEvent) -> None:
if disabled or e.key != key_num:
return
state = me.state(State)
input_key, output_key = get_in_out_keys()
setattr(state, input_key, e.value)
setattr(state, output_key, e.value)

def get_input_key() -> str:
return f"{key}_input"

def get_output_key() -> str:
return f"{key}_output"

def get_in_out_keys() -> list[str]:
return [get_input_key(), get_output_key()]

def clear_in_out() -> None:
input_key, output_key = get_in_out_keys()
setattr(state, input_key, "")
setattr(state, output_key, "")

state = me.state(State)
key_num = f"{key}{len(state.conversation.messages)}"
with me.box(
style=me.Style(
border_radius=16,
Expand All @@ -29,50 +62,38 @@ def _on_feedback_blur(e: me.InputBlurEvent) -> None:
width="100%",
)
):
optional_value = {"value": value} if value is not None else {}
if disabled:
in_value = value
key_num = f"{key}disabled{len(state.conversation.messages)}"
else:
input_key = get_input_key()
in_value = getattr(state, input_key)
key_num = f"{key}{len(state.conversation.messages)}"

with me.box(style=me.Style(flex_grow=1)):
me.native_textarea(
placeholder="Provide a feedback to the team",
on_blur=_on_feedback_blur,
on_blur=on_blur,
key=key_num,
autosize=True,
min_rows=3,
max_rows=10,
readonly=disabled,
shortcuts={
me.Shortcut(key="enter", shift=True): on_newline,
me.Shortcut(key="enter"): on_submit,
},
style=me.Style(
padding=me.Padding(top=16, left=16),
outline="none",
width="100%",
border=me.Border.all(me.BorderSide(style="none")),
),
**optional_value,
value=in_value,
)

with me.content_button(
type="icon",
on_click=send_feedback,
on_click=on_click,
disabled=disabled,
):
me.icon("send")


def input_prompt(send_prompt: Callable[[me.ClickEvent], Iterator[None]]) -> None:
with me.box(
style=me.Style(
border_radius=16,
padding=me.Padding.all(8),
background="white",
display="flex",
width="100%",
)
):
with me.box(style=me.Style(flex_grow=1)):
me.native_textarea(
placeholder="Enter a prompt",
key="prompt",
on_blur=_on_blur,
style=me.Style(
padding=me.Padding(top=16, left=16),
outline="none",
width="100%",
border=me.Border.all(me.BorderSide(style="none")),
),
)
with me.content_button(type="icon", on_click=send_prompt):
me.icon("send")
3 changes: 2 additions & 1 deletion fastagency/ui/mesop/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class Conversation:
@me.stateclass
class State:
in_conversation: bool = False # True when in active conversation, or past one.
prompt: str = ""
prompt_input: str = ""
prompt_output: str = ""
conversation: Conversation
past_conversations: list[Conversation] = field(default_factory=list)
hide_past: bool = True
35 changes: 23 additions & 12 deletions fastagency/ui/mesop/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import time
from collections.abc import Iterator

import mesop as me

from ...logging import get_logger
from .base import MesopUI
from .components.inputs import input_prompt
from .components.inputs import input_text
from .components.ui_common import header
from .data_model import Conversation, State
from .message import consume_responses, message_box
Expand Down Expand Up @@ -44,7 +45,13 @@ def home_page() -> None:


def past_conversations_box() -> None:
def select_past_conversation(ev: me.ClickEvent) -> None:
def conversation_display_title(full_name: str, max_length: int) -> str:
if len(full_name) <= max_length:
return full_name
else:
return full_name[: max_length - 3] + "..."

def select_past_conversation(ev: me.ClickEvent) -> Iterator[None]:
id = ev.key
state = me.state(State)
conversations_with_id = list(
Expand All @@ -53,12 +60,18 @@ def select_past_conversation(ev: me.ClickEvent) -> None:
conversation = conversations_with_id[0]
state.conversation = conversation
state.in_conversation = True
yield
time.sleep(1)
yield
me.scroll_into_view(key="conversationtop")
yield

def on_show_hide(ev: me.ClickEvent) -> None:
state.hide_past = not state.hide_past

def on_start_new_conversation(ev: me.ClickEvent) -> None:
state.in_conversation = False
state.prompt = ""

state = me.state(State)
style = PAST_CHATS_HIDE_STYLE if state.hide_past else PAST_CHATS_SHOW_STYLE
Expand Down Expand Up @@ -90,9 +103,7 @@ def on_start_new_conversation(ev: me.ClickEvent) -> None:
border_radius=16,
),
):
me.text(
text=conversation.title,
)
me.text(text=conversation_display_title(conversation.title, 128))


def conversation_starter_box() -> None:
Expand All @@ -108,19 +119,17 @@ def conversation_starter_box() -> None:
"Enter a prompt to chat with FastAgency team",
style=me.Style(font_size=20, margin=me.Margin(bottom=24)),
)
input_prompt(send_prompt)
input_text(send_prompt, "prompt", disabled=False)


def send_prompt(e: me.ClickEvent) -> Iterator[None]:
def send_prompt(prompt: str) -> Iterator[None]:
ui = get_ui()
wf = ui.app.wf

name = wf.names[0]

state = me.state(State)
# me.navigate("/conversation")
prompt = state.prompt
state.prompt = ""
conversation = Conversation(
title=prompt, completed=False, waiting_for_feedback=False
)
Expand All @@ -138,10 +147,12 @@ def conversation_box() -> None:
header()
messages = conversation.messages
with me.box(
style=me.Style(
overflow_y="auto",
)
style=me.Style(overflow_y="auto", display="flex", flex_direction="column")
):
me.box(
key="conversationtop",
style=me.Style(margin=me.Margin(bottom="1vh")),
)
for message in messages:
message_box(message, conversation.is_from_the_past)
if messages:
Expand Down
23 changes: 17 additions & 6 deletions fastagency/ui/mesop/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from fastagency.base import AskingMessage, WorkflowCompleted
from fastagency.ui.mesop.base import MesopMessage
from fastagency.ui.mesop.components.inputs import input_user_feedback
from fastagency.ui.mesop.components.inputs import input_text
from fastagency.ui.mesop.send_prompt import send_user_feedback_to_autogen

from ...base import (
Expand Down Expand Up @@ -136,6 +136,8 @@ def visit_text_message(self, message: TextMessage) -> None:
background=base_color,
padding=me.Padding.all(16),
border_radius=16,
align_self="flex-start",
width="95%",
margin=me.Margin.symmetric(vertical=16),
)
):
Expand All @@ -156,9 +158,7 @@ def visit_system_message(self, message: SystemMessage) -> None:
me.markdown(json.dumps(message.message, indent=2))

def visit_text_input(self, message: TextInput) -> str:
def on_input(ev: me.RadioChangeEvent) -> Iterator[None]:
state = me.state(State)
feedback = state.conversation.feedback
def on_input(feedback: str) -> Iterator[None]:
self._conversation_message.feedback = [feedback]
self._conversation_message.feedback_completed = True
yield from self._provide_feedback(feedback)
Expand All @@ -171,20 +171,23 @@ def value_if_completed() -> Optional[str]:
prompt = message.prompt if message.prompt else "Please enter a value"
if message.suggestions:
suggestions = ",".join(suggestion for suggestion in message.suggestions)
prompt += "\n" + suggestions
prompt += "\n Suggestions: " + suggestions

with me.box(
style=me.Style(
background=base_color,
padding=me.Padding.all(16),
border_radius=16,
align_self="flex-end",
width="95%",
margin=me.Margin.symmetric(vertical=16),
)
):
self._header(message, base_color, title="Input requested")
me.markdown(prompt)
input_user_feedback(
input_text(
on_input,
"prompt",
disabled=self._readonly or self._has_feedback(),
value=value_if_completed(),
)
Expand Down Expand Up @@ -222,6 +225,8 @@ def on_change(ev: me.RadioChangeEvent) -> Iterator[None]:
background=base_color,
padding=me.Padding.all(16),
border_radius=16,
align_self="flex-end",
width="95%",
margin=me.Margin.symmetric(vertical=16),
)
):
Expand Down Expand Up @@ -264,6 +269,8 @@ def should_be_checked(option: str) -> bool:
style=me.Style(
background=base_color,
padding=me.Padding.all(16),
align_self="flex-end",
width="95%",
border_radius=16,
margin=me.Margin.symmetric(vertical=16),
)
Expand Down Expand Up @@ -291,6 +298,8 @@ def visit_suggested_function_call(
style=me.Style(
background=base_color,
padding=me.Padding.all(16),
align_self="flex-start",
width="95%",
border_radius=16,
margin=me.Margin.symmetric(vertical=16),
)
Expand All @@ -309,6 +318,8 @@ def visit_function_call_execution(
style=me.Style(
background=base_color,
padding=me.Padding.all(16),
align_self="flex-start",
width="95%",
border_radius=16,
margin=me.Margin.symmetric(vertical=16),
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ dependencies = [
# public distributions

mesop = [
"mesop>=0.12, <1; python_version >= '3.10'"
"mesop>=0.12.4, <1; python_version >= '3.10'"
]

pyautogen = [
Expand Down

0 comments on commit f42290e

Please sign in to comment.