Skip to content

Your own UI

agent_chat is optional. AgentState is a plain Reflex state: read its vars, call its event handlers, and build any UI you like from Reflex components.

from typing import ClassVar

import reflex as rx

from reflex_agentkit import AgentState, HttpAGUIClient


class ChatState(AgentState, rx.State):
    agent: ClassVar = HttpAGUIClient("http://localhost:8000/agent")
    draft: str = ""

    @rx.event
    def set_draft(self, value: str) -> None:
        self.draft = value

    @rx.event
    def submit(self):
        text, self.draft = self.draft, ""
        return ChatState.send_message(text)


def index() -> rx.Component:
    return rx.vstack(
        rx.foreach(ChatState.messages, lambda m: rx.text(m.role, ": ", m.content)),
        rx.cond(ChatState.error != "", rx.callout(ChatState.error, color_scheme="red")),
        rx.hstack(
            rx.input(value=ChatState.draft, on_change=ChatState.set_draft),
            rx.button("Send", on_click=ChatState.submit, disabled=~ChatState.can_send),
            rx.button("Stop", on_click=ChatState.cancel_run, disabled=~ChatState.is_running),
        ),
    )


app = rx.App()
app.add_page(index)

Mixing in the components

Each part of agent_chat is a component of its own, so you can use only the pieces you need: message_list, composer, tool_call_card, approval_dialog, run_status_bar and shared_state_panel. They all take the state class first. See Components.

from reflex_agentkit.components import approval_dialog, composer, message_list


def index() -> rx.Component:
    return rx.container(
        rx.heading("Support"),
        message_list(ChatState),
        composer(ChatState, placeholder="Describe the problem"),
        approval_dialog(ChatState),
    )

Per-user clients and run input

Override the hooks to adapt each run to the signed-in user. They are plain methods, not event handlers, and the browser can never call them.

class ChatState(AgentState, rx.State):
    def create_client(self) -> HttpAGUIClient:
        token = lookup_token(self.router.session.client_token)  # your own backend lookup
        return HttpAGUIClient(
            "https://agents.internal/support",
            headers={"Authorization": f"Bearer {token}"},
        )

    def prepare_run_input(self, run_input):
        return run_input.model_copy(update={"forwarded_props": {"locale": "es"}})

    async def on_run_end(self, status) -> None:
        record_metrics(self.thread_id, status)  # must be quick: it runs under the state lock

The client a hook returns lives only in backend memory: it never becomes a state var.