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 mini program SDK's im.ai is the same as the Web SDK's: it joins live content in order, resumes after disconnects and fills in after reconnecting, and once the reply ends the message body is authoritative. The APIs, fields and events are described fully in the Web SDK's AI replies; this page covers displaying them in mini program pages. Bots and replies are created by your server, see AI conversations.
Version
AI replies are available from @deeprespond/im-miniprogram@1.0.6; see Overview and installation to install.
Turn it into a view
Mini programs cannot render Markdown directly. The SDK's render.aiView(message, live) turns an AI message into a view the template can use as is, converting the body into a rich-text node array (it only produces nodes, never HTML strings; links keep only their text):
const { render } = require('@deeprespond/im-miniprogram');
const view = render.aiView(message); // 已结束:取消息正文
const live = render.aiView(message, im.ai.state(message.body.stream_id)); // 生成中:取实时内容| Field | Description |
|---|---|
stream_id, status | Stream ID; streaming, completed, stopped, failed |
streaming, thinking, waiting | Generating; only reasoning so far, no body yet (show "thinking"); nothing yet (show "replying") |
reasoning | Reasoning text |
tools | Tool calls: id, title, state (running, done, failed; still running when the reply ended becomes canceled) |
blocks | Blocks of the body: kind is text (nodes are rich-text nodes, last marks the last block, for the cursor), image or file (name, size, ext) |
error | Explanation to show when interrupted; empty string otherwise |
text | Plain text for copying |
Returns null for messages that are not AI messages.
Follow content while generating
AI messages in the message list are snapshots of the body; the live content changes with ai.streamUpdated. Keep a live view per stream_id with a page binding that recomputes on these events:
Page({
behaviors: [app.behavior],
onLoad({ key }) {
// 整理消息列表时附上正文的视图(结束后以它为准)
this.drMessageList('chat', key, { mapItem: (item) => ({ ...item, ai: render.aiView(item) }) });
this.drState('aiLive', (im) => {
const items = this.drListOf('chat')?.getSnapshot().items || [];
const live = {};
for (const m of items) {
if (m.type !== 'ai' || m.body?.status !== 'streaming') continue;
const state = im.ai.state(m.body.stream_id);
// 键加前缀:setData 的路径中纯数字的键会被当作数组下标
if (state) live['s' + m.body.stream_id] = render.aiView(m, state);
}
return live;
}, ['ai.streamStarted', 'ai.streamUpdated', 'ai.streamEnded'], { lists: ['chat'] });
},
stopAI(e) {
app.im.ai.stop(e.currentTarget.dataset.stream).catch((error) => app.report(error));
},
});To follow a single reply, you can also bind its live state with DR_STATES.aiStream(streamId) from the /bind entry (the result of im.ai.state(), null after it ends).
In the message loop, prefer the live view and fall back to the view built from the message body in mapItem once the reply ends:
<template name="ai">
<view class="ai-msg" data-status="{{ai.status}}">
<view wx:if="{{ai.reasoning}}" class="ai-reasoning">{{ai.thinking ? '思考中…' : '已深度思考'}}</view>
<view wx:for="{{ai.tools}}" wx:for-item="tool" wx:key="id" class="ai-tool ai-tool-{{tool.state}}">{{tool.title}}</view>
<block wx:for="{{ai.blocks}}" wx:for-item="block" wx:key="index">
<view wx:if="{{block.kind === 'text'}}">
<rich-text nodes="{{block.nodes}}" user-select/><text wx:if="{{ai.streaming && block.last}}">▍</text>
</view>
<image wx:elif="{{block.kind === 'image'}}" src="{{block.url}}" mode="widthFix"/>
<view wx:elif="{{block.kind === 'file'}}">{{block.name}} {{block.size}}</view>
</block>
<view wx:if="{{ai.status === 'stopped'}}">已停止生成</view>
<view wx:if="{{ai.status === 'failed'}}">生成中断:{{ai.error}}</view>
<button wx:if="{{ai.streaming}}" size="mini" data-stream="{{ai.stream_id}}" catchtap="stopAI">停止生成</button>
</view>
</template>
<view wx:for="{{chat.items}}" wx:key="_key">
<template wx:if="{{item.type === 'ai'}}" is="ai" data="{{ai: aiLive['s' + item.body.stream_id] || item.ai}}"/>
</view>The rich-text nodes carry classes such as ai-p, ai-h, ai-list, ai-li, ai-quote, ai-code, ai-inline-code, ai-strong, ai-em, ai-link, ai-table, ai-tr, ai-th and ai-td, which you can style in the page stylesheet.
- Stop generating: only the user receiving the reply can stop it; stopping a reply that already ended returns its current state without an error.
- Regenerate: send the answered question again and the bot's server starts a new reply.
- After disconnects or returning from the background, the SDK fills in content still being generated.
Example
The chat page of the example mini program already displays AI replies (pages/chat), including reasoning, tool calls, the Markdown body, files, stop generating, copying and interruption notices, and can serve as a reference.
