Skip to content

DuoChat Codelab Part 2: Building the basic UI

In this section, we'll create the main layout for our DuoChat application, including the header, chat input area, and some basic styling. We'll use Mesop's components and styling system to create an attractive and functional UI.

Updating the Main Layout

Let's start by updating our main.py file to include a more structured layout. We'll use Mesop's box component for layout and add some custom styles.

Replace the content of main.py with the following:

main.py
import mesop as me

ROOT_BOX_STYLE = me.Style(
    background="#e7f2ff",
    height="100%",
    font_family="Inter",
    display="flex",
    flex_direction="column",
)

@me.page(
    path="/",
    stylesheets=[
        "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
    ],
)
def page():
    with me.box(style=ROOT_BOX_STYLE):
        header()
        with me.box(
            style=me.Style(
                width="min(680px, 100%)",
                margin=me.Margin.symmetric(
                    horizontal="auto",
                    vertical=36,
                ),
            )
        ):
            me.text(
                "Chat with multiple models at once",
                style=me.Style(
                    font_size=20,
                    margin=me.Margin(bottom=24),
                ),
            )
            chat_input()

def header():
    with me.box(
        style=me.Style(
            padding=me.Padding.all(16),
        ),
    ):
        me.text(
            "DuoChat",
            style=me.Style(
                font_weight=500,
                font_size=24,
                color="#3D3929",
                letter_spacing="0.3px",
            ),
        )

def chat_input():
    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",
                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"):
            me.icon("send")

Run the Mesop app and look at the changes:

mesop main.py

Let's review the changes:

  1. We've added a ROOT_BOX_STYLE to set the overall layout and background color.
  2. We're importing a custom font (Inter) using the stylesheets parameter in the @me.page decorator.
  3. We've created separate functions for the header and chat_input components.
  4. The main layout uses nested box components with custom styles to create a centered, responsive design.

Understanding Mesop's Styling System

Mesop's styling system is based on Python classes that correspond to CSS properties. You can learn more by reading the Style API docs.

Adding Interactivity

Now, let's add some basic interactivity to our chat input. We'll update the chat_input function to handle user input:

main.py
@me.stateclass
class State:
    input: str = ""

def on_blur(e: me.InputBlurEvent):
    state = me.state(State)
    state.input = e.value

def chat_input():
    state = me.state(State)
    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(
                value=state.input,
                placeholder="Enter a 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")

def send_prompt(e: me.ClickEvent):
    state = me.state(State)
    print(f"Sending prompt: {state.input}")
    state.input = ""

Here's what we've added:

  1. A State class to manage the application state, including the user's input.
  2. An on_blur function to update the state when the user switches focus from the textarea.
  3. A send_prompt function that will be called when the send button is clicked.

Running the Updated Application

Run the application again with mesop main.py and navigate to http://localhost:32123. You should now see a styled header, a centered layout, and a functional chat input area.

Troubleshooting

If you're having trouble, compare your code to the solution.

Next Steps

In the next section, we'll dive deeper into state management and implement the model picker dialog.

Managing state & dialogs