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. The Web SDK's 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. React UIKit and Vue UIKit already display AI replies. Bots and replies are created by your server, see AI conversations.
Version
AI replies are available from npm 1.0.6: @deeprespond/im-web, @deeprespond/im-web-react, @deeprespond/im-web-vue, @deeprespond/im-web-react-uikit and @deeprespond/im-web-vue-uikit. See Overview and installation to install.
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 message.type is ai, read the body with im.ai.bodyOf(message):
const body = im.ai.bodyOf(message); // 不是 ai 消息时为 null
if (body) {
console.log(body.status); // 'streaming' | 'completed' | 'stopped' | 'failed'
console.log(body.parts); // 结束后的完整内容;生成中为空数组
}| Field | Description |
|---|---|
stream_id | Stream ID of the reply, used to read live content and to stop generation |
status | streaming generating, completed, stopped, failed interrupted |
parts | Parts: 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 conversation previews and copying |
finish_reason, error | Finish reason; when interrupted, error.message is the explanation to show the user |
Parts are described fully in AI messages. Conversation previews, unread counts and search use text like normal messages.
Display content while generating
A generating message has empty parts; the live content is in im.ai.state(streamId), and every update fires ai.streamUpdated:
function renderAI(message: MessageView) {
const body = im.ai.bodyOf(message);
if (!body) return;
const parts = body.status === 'streaming' ? im.ai.state(body.stream_id)?.parts ?? [] : body.parts;
draw(message, parts, body.status);
}
im.on('ai.streamUpdated', (state) => {
// state.stream_id、state.parts(当前完整内容,不是增量)、state.seq
redrawByStream(state.stream_id, state.parts);
});
im.on('ai.streamEnded', ({ stream_id, status }) => {
// 回复结束:随后(或已经)收到这条消息的 messages.changed,重新读取消息、以正文为准
});| Event | When |
|---|---|
ai.streamStarted | A generating AI message arrives |
ai.streamUpdated | Content changed: new content arrived, or was filled in after a disconnect or reconnect; parts is the full current content |
ai.streamEnded | The reply ended; the final content is in the message body, which is updated with messages.changed |
- Pushes are merged every 50 ms or 2 KB; simply redraw from
parts. - After disconnects, returning from the background, page refreshes or logging in on another device, the SDK fills in the reply being generated automatically.
- When several tabs share one connection, every tab receives these events.
im.ai.state()returnsnullafter the reply ends; read the message body instead.
In React and Vue
The React and Vue bindings provide useAIStream(streamId), which returns im.ai.state(streamId), updates with the ai.* events, and becomes null after the reply ends:
// React(@deeprespond/im-web-react)
function AIMessage({ message }: { message: MessageView }) {
const im = useDRClient();
const body = im?.ai.bodyOf(message);
const live = useAIStream(body?.status === 'streaming' ? body.stream_id : null);
const parts = live?.parts ?? body?.parts ?? [];
return <AIParts parts={parts} streaming={body?.status === 'streaming'} />;
}// Vue(@deeprespond/im-web-vue):参数可以是 ref 或 getter
const live = useAIStream(() => (body.value?.status === 'streaming' ? body.value.stream_id : null));Turn it into a view
When drawing it yourself, aiView(message, live) from @deeprespond/im-web/render turns an AI message into a view: reasoning, tool-call states (those still running when the reply ended are marked canceled), body blocks (Markdown converted to a node array, links keep only their text), attachments, the interruption explanation and plain text for copying; markdownNodes(text) converts a single piece of Markdown. The mini program, uni-app and Tauri examples all use it; its fields are listed in the mini program SDK's AI replies.
import { aiView } from '@deeprespond/im-web/render';
const view = aiView(message, im.ai.state(streamId)); // 不是 ai 消息时为 nullStop generating
const stream = await im.ai.stop(body.stream_id);
// stream.status 为 'stopped';已结束的回复返回当前状态,不报错Only the user receiving the reply can stop it. The reply ends immediately, generated content is kept, and the bot's server is notified.
To read a reply's current content from the server (for troubleshooting), use im.ai.get(streamId); for replies that ended more than 10 minutes ago it returns not_found, read the message instead.
Regenerate
There is no separate "regenerate" API: send the question again and the bot's server starts a new reply. An AI message's reply_to points to the message it answers.
React UIKit
The React UIKit chat view displays AI messages without extra configuration:
- Body text rendered as Markdown (headings, lists, quotes, code blocks, tables, links), with a cursor at the end while generating;
- Reasoning collapsed by default, showing "thinking" while generating;
- Tool-call progress (running, done, failed, canceled);
- Image and file attachments;
- A "Stop" button while generating; "Copy" and "Regenerate" after the end;
- Notes for stopped and interrupted replies (with the reason).
When customizing the message list, AI messages can still be displayed with the UIKit's exported MessageBubble, or drawn yourself as in the previous section.
Vue UIKit
The Vue UIKit chat view displays AI messages the same way as React UIKit. "Regenerate" appears when the AI message quotes the user's question (reply_to) and that question is in the current message list; clicking it sends the question again. When using MessageBubble on its own, provide the quoted message through findMessage(seq).
