AIConversation
The messages of a conversation with an AI agent, oldest first, shown your own way.
AIConversation(
messages = messages,
key = { it.id },
isFromUser = { it.isMine },
isThinking = isWaitingForReply,
footer = { error?.let { RetryButton(it) } },
emptyContent = { Text("How can I help?") },
) { message ->
MessageRow(message)
}Each message the person sends starts a turn: that message and the replies after it. The latest turn is at least as tall as the list, so when the person sends a message, the list scrolls it to the top and the reply grows below it without moving anything on screen. A conversation that loads, or replaces the one shown, opens at its end, and a button brings the end back into view whenever it is out of it.
Place the composer below the list, so the list knows how much of it is visible. To float the composer over the list instead, pass the space it covers as contentPadding:
var composerHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Box {
AIConversation(
messages = messages,
key = { it.id },
isFromUser = { it.isMine },
contentPadding = PaddingValues(bottom = composerHeight),
) { message ->
MessageRow(message)
}
Composer(
modifier = Modifier
.align(Alignment.BottomCenter)
.onSizeChanged { composerHeight = with(density) { it.height.toDp() } },
)
}Parameters
The conversation, oldest message first.
A stable, unique key for a message. A turn is known by its first message's key, so what is on screen stays in place as messages come and go around it. The key must be saveable in a Bundle, as with any lazy list key.
Whether the person using the app sent a message. Each of their messages starts a turn.
The modifier to apply to the list.
Whether the agent is answering the latest message and its reply shows nothing yet. Meanwhile the latest turn ends with AIThinking.
Loads the messages before the first one, which the list asks for as the person scrolls to its start. It shows a spinner meanwhile, and what is on screen stays in place as they arrive: the message that was first keeps starting a turn, even when the person's message before it arrives. Pass null once there are none left.
Content under the latest turn's messages, such as what the agent is doing, or an error with a way to retry.
What the list shows while there are no messages, centered in it.
The palette of the list's own views: the button that brings the end back into view, and the spinner while older messages load.
The icons of the list's own views.
Space at the edges of the list that something drawn over it covers, such as a composer floating over its bottom. The messages scroll under it but stop clear of it, the latest turn is at least as tall as the part of the list it leaves visible, emptyContent is centered in that part, and the button that brings the end back into view and the spinner while older messages load show inside it. It adds to the list's own spacing.
The view of a message.
Shows AIMessages with the views of LocalChatAiComponentFactory: each message the person sent with ChatAiComponentFactory.AIConversationUserMessage, each reply with ChatAiComponentFactory.AIConversationAssistantMessage, and under a finished reply, once its answer is written out, ChatAiComponentFactory.AIConversationReplyActions.
A reply joins the list once it shows something; until then the list shows the agent thinking.
To float the composer over the list, pass the space it covers as contentPadding:
var composerHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Box {
AIConversation(
messages = messages,
contentPadding = PaddingValues(bottom = composerHeight),
)
ChatComposer(
onSendClick = ::send,
onStopClick = ::stop,
isGenerating = isGenerating,
modifier = Modifier
.align(Alignment.BottomCenter)
.onSizeChanged { composerHeight = with(density) { it.height.toDp() } },
)
}Parameters
The conversation, oldest message first.
The modifier to apply.
A message the person sent that is not in messages yet. Keep passing it until its reply starts: the list stops showing it once its copy arrives.
Whether the agent is answering the latest message and its reply shows nothing yet. The list also knows it is while pendingMessage waits, and while the latest reply is being written but shows nothing.
Who answers tool calls' questions and sign-ins on this device, to ask them.
Loads the messages before the first one, which the list asks for as the person scrolls to its start. It shows a spinner meanwhile, and what is on screen stays in place as they arrive. Pass null once there are none left.
A view under the latest turn's messages, such as what the agent is doing, or an error with a way to retry.
What the list shows while there are no messages, centered in it.
The palette of the list's own views: the button that brings the end back into view, and the spinner while older messages load.
The icons of the list's own views.
Space at the edges of the list that something drawn over it covers, such as a composer floating over its bottom. The messages scroll under it but stop clear of it, and the list's own views show inside it.