AI replies
When a user chats one-to-one with an AI bot, the bot's reply is a message of type ai whose content is pushed while it is being generated. im.ai receives this live content: it joins it in order, resumes after disconnects and fills in after reconnecting, and once the reply ends the message body is authoritative. You only need to display it. Bots and replies are created by your server, see AI conversations.
Version
AI replies are available from SDK 2.0.1; see Demo guide and download to download it.
Bots
In the client a bot is a normal user: its profile's kind is bot, it can be added as a friend and appears in the conversation list, and users simply send it text messages. Bots cannot be added to groups; inviting one fails with permission_denied (details.reason is bot_account).
AI messages
When MessageView.type is ai, read the body with im.ai.bodyOf(message):
val body = im.ai.bodyOf(message) ?: return // 不是 ai 消息时为 null
println(body.status) // streaming、completed、stopped、failed
println(body.parts) // 结束后的完整内容;生成中为空| Field | Description |
|---|---|
streamId | Stream ID of the reply, used to read live content and to stop generation |
status | streaming generating, completed, stopped, failed interrupted |
parts | Parts (AIPart): text body (Markdown), reasoning, image and file attachments, tool tool-call progress (state is running, done or failed) |
text | Plain text without Markdown, up to 2000 characters, for copying and previews |
finishReason, error | Finish reason; when interrupted, error.message is the explanation to show the user |
Parts are described fully in AI messages. Conversation previews (summarize supports AI messages, with wording for generating, stopped and interrupted replies), unread counts and search work like normal messages.
Display content while generating
While a message is generating its parts is empty; the live content is in im.ai.state(streamId), and every update emits DREvent.AIStreamUpdated:
fun partsOf(message: MessageView): List<AIPart> {
val body = im.ai.bodyOf(message) ?: return emptyList()
return if (body.status == "streaming") im.ai.state(body.streamId)?.parts.orEmpty() else body.parts
}
lifecycleScope.launch {
im.events.collect { event ->
when (event) {
// state.parts 是当前完整内容(不是增量),按它整体重画这条消息
is DREvent.AIStreamUpdated -> redrawByStream(event.state.streamId)
is DREvent.AIStreamStarted -> redrawByStream(event.state.streamId)
// 回复结束:消息随后(或已经)以 MessagesChanged 更新为最终内容
is DREvent.AIStreamEnded -> redrawByStream(event.streamId)
else -> {}
}
}
}| Event | When |
|---|---|
AIStreamStarted | A generating AI message arrives |
AIStreamUpdated | The content changes: new content arrived, or it was filled in after a disconnect or reconnect; state.parts is the full current content |
AIStreamEnded | The reply ended (status is completed, stopped or failed); the final content is in the message body |
- Pushes are merged into one every 50 ms or 2 KB. While generating, redraw only this message; do not rebuild the whole message list on every update.
- After disconnects, returning from the background or restarting the app, the SDK fills in content still being generated; you do not need to handle it.
im.ai.state()returnsnullafter the reply ends; read the message body from then on.
Stop generating
val stream = im.ai.stop(body.streamId)
// stream.status 为 stopped;已结束的回复返回当前状态,不报错Only the user receiving the reply can stop it. Stopping ends the reply immediately, keeps what was generated, and notifies the bot's server. To read a reply's current content from the server, use im.ai.get(streamId); replies that ended more than 10 minutes ago fail with not_found, so read the message instead.
From Java, use the same methods through im.ai(): stop and get provide callback and ...Async() entry points, see Java.
Regenerate
There is no separate "regenerate" API: send the answered question again and the bot's server starts a new reply. An AI message's replyTo points to the message it answers.
Example app
The chat screen of the example app already displays AI replies (AIBubble.kt) and can serve as a reference:
- The body is rendered as Markdown (headings, bold, inline code, lists, quotes, code blocks, tables), with a cursor at the end while generating;
- Reasoning, tool-call progress (running, done, failed, canceled) and file attachments;
- "Stop generating" while generating, "Copy" afterwards; notices for stopped and interrupted replies (with the reason).
AI replies can be long, so give the bubble a fixed width (for example the maximum width of the content area). When the width is negotiated between an outer wrap_content and match_parent, long text can be measured at one width and laid out at another, cutting off its last lines.
