useXChat 数据管理
配合 Agent hook 进行对话数据管理。
何时使用
通过 Agent 进行会话数据管理,并产出供页面渲染使用的数据。
代码演示
基本
基础用法。
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Flex } from 'ant-design-vue';
import { Bubble, Sender, useXAgent, useXChat, type BubbleListProps } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'AXUseXChatBasic' });
const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
typing: { step: 5, interval: 20 },
style: {
maxWidth: '600px',
},
},
local: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
const mockSuccess = ref(false);
const content = ref('');
const senderLoading = ref(false);
const setContent = (v: string) => {
content.value = v;
}
// Agent for request
const [agent] = useXAgent({
request: async ({ message }, { onSuccess, onError }) => {
senderLoading.value = true;
await sleep();
senderLoading.value = false;
mockSuccess.value = !mockSuccess.value;
if (mockSuccess.value) {
onSuccess(`Mock success return. You said: ${message}`);
}
onError(new Error('Mock request failed'));
},
});
// Chat messages
const { onRequest, messages } = useXChat({
agent: agent.value,
requestPlaceholder: 'Waiting...',
requestFallback: 'Mock failed return. Please try again later.',
});
defineRender(() => {
return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: '300px' }}
items={messages.value.map(({ id, message, status }) => ({
key: id,
loading: status === 'loading',
role: status === 'local' ? 'local' : 'ai',
content: message,
}))}
/>
<Sender
loading={senderLoading.value}
value={content.value}
onChange={setContent}
onSubmit={(nextContent) => {
onRequest(nextContent);
setContent('');
}}
/>
</Flex>
)
});
</script>
隐藏源代码
流式输出
使用流式输出更新内容。
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Flex } from 'ant-design-vue';
import { Bubble, Sender, useXAgent, useXChat } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'AXUseXChatStream' });
const roles: (typeof Bubble.List)['roles'] = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
},
local: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
const content = ref('');
const senderLoading = ref(false);
// Agent for request
const [ agent ] = useXAgent({
request: async ({ message }, { onSuccess, onUpdate }) => {
senderLoading.value = true;
const fullContent = `Streaming output instead of Bubble typing effect. You typed: ${message}`;
let currentContent = '';
const id = setInterval(() => {
currentContent = fullContent.slice(0, currentContent.length + 2);
onUpdate(currentContent);
if (currentContent === fullContent) {
senderLoading.value = false;
clearInterval(id);
onSuccess(fullContent);
}
}, 100);
},
});
// Chat messages
const { onRequest, messages } = useXChat({
agent: agent.value,
});
defineRender(() => {
return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: 300 }}
items={messages.value.map(({ id, message, status }) => ({
key: id,
role: status === 'local' ? 'local' : 'ai',
content: message,
}))}
/>
<Sender
loading={senderLoading.value}
value={content.value}
onChange={(v) => content.value = v}
onSubmit={(nextContent) => {
onRequest(nextContent);
content.value = '';
}}
/>
</Flex>
)
});
</script>
隐藏源代码
打断输出
打断正在流式输出的内容。
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Flex } from 'ant-design-vue';
import { Bubble, Sender, useXAgent, useXChat, XStream, type BubbleListProps } from 'ant-design-x-vue';
import { onWatcherCleanup, ref, watchEffect } from 'vue';
defineOptions({ name: 'AXUseXChatStreamCancel' });
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
},
local: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
const contentChunks = [
'He',
'llo',
', w',
'or',
'ld!',
' Ant',
' Design',
' X',
' is',
' the',
' best',
'!',
];
function mockReadableStream() {
const sseChunks: string[] = [];
for (let i = 0; i < contentChunks.length; i++) {
const sseEventPart = `event: message\ndata: {"id":"${i}","content":"${contentChunks[i]}"}\n\n`;
sseChunks.push(sseEventPart);
}
return new ReadableStream({
async start(controller) {
for (const chunk of sseChunks) {
await new Promise((resolve) => setTimeout(resolve, 300));
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
});
}
const content = ref('');
const senderLoading = ref(false);
const abort = ref(() => { });
watchEffect(() => {
onWatcherCleanup(() => {
abort.value();
})
});
// Agent for request
const [ agent ] = useXAgent({
request: async (_, { onSuccess, onUpdate }) => {
senderLoading.value = true;
const stream = XStream({
readableStream: mockReadableStream(),
});
const reader = stream.getReader();
abort.value = () => {
reader?.cancel();
};
let current = '';
while (reader) {
const { value, done } = await reader.read();
if (done) {
senderLoading.value = false;
onSuccess(current);
break;
}
if (!value) continue;
const data = JSON.parse(value.data);
current += data.content || '';
onUpdate(current);
}
},
});
// Chat messages
const { onRequest, messages } = useXChat({
agent: agent.value,
});
defineRender(() => {
return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: 300 }}
items={messages.value.map(({ id, message, status }) => ({
key: id,
role: status === 'local' ? 'local' : 'ai',
content: message,
}))}
/>
<Sender
loading={senderLoading.value}
value={content.value}
onChange={(v) => content.value = v}
onSubmit={(nextContent) => {
onRequest(nextContent);
content.value = '';
}}
onCancel={() => abort.value()}
/>
</Flex>
)
});
</script>
隐藏源代码
多项建议
通过定制能力,返回多个推荐内容。
Hello, what can I do for you?
ts
<script setup lang="tsx">
import { SmileOutlined, UserOutlined } from '@ant-design/icons-vue';
import { Flex } from 'ant-design-vue';
import { Bubble, Prompts, Sender, useXAgent, useXChat, type BubbleListProps } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'AXUseXChatSuggestions' });
const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));
const roles: BubbleListProps['roles'] = {
user: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
text: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
typing: true,
},
suggestion: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { visibility: 'hidden' } },
variant: 'borderless',
messageRender: (content) => (
<Prompts
vertical
items={(content as any as string[]).map((text) => ({
key: text,
icon: <SmileOutlined style={{ color: '#FAAD14' }} />,
description: text,
}))}
/>
),
},
};
type AgentUserMessage = {
type: 'user';
content: string;
};
type AgentAIMessage = {
type: 'ai';
content?: string;
list?: (
| {
type: 'text';
content: string;
}
| {
type: 'suggestion';
content: string[];
}
)[];
};
type AgentMessage = AgentUserMessage | AgentAIMessage;
type BubbleMessage = {
role: string;
};
const content = ref('');
const senderLoading = ref(false);
// Agent for request
const [ agent ] = useXAgent<AgentMessage>({
request: async ({ message }, { onSuccess }) => {
senderLoading.value = true;
await sleep();
const { content } = message || {};
senderLoading.value = false;
onSuccess({
type: 'ai',
list: [
{
type: 'text',
content: `Do you want?`,
},
{
type: 'suggestion',
content: [`Look at: ${content}`, `Search: ${content}`, `Try: ${content}`],
},
],
});
},
});
// Chat messages
const { onRequest, parsedMessages } = useXChat<AgentMessage, BubbleMessage>({
agent: agent.value,
defaultMessages: [
{
id: 'init',
message: {
type: 'ai',
content: 'Hello, what can I do for you?',
},
status: 'success',
},
],
requestPlaceholder: {
type: 'ai',
content: 'Waiting...',
},
// Convert AgentMessage to BubbleMessage
parser: (agentMessages) => {
const list = agentMessages.content ? [agentMessages] : (agentMessages as AgentAIMessage).list;
return (list || []).map((msg) => ({
role: msg.type,
content: msg.content,
}));
},
});
defineRender(() => {
return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: 300 }}
items={parsedMessages.value.map(({ id, message, status }) => ({
key: id,
loading: status === 'loading',
...message,
}))}
/>
<Sender
loading={senderLoading.value}
value={content.value}
onChange={(v) => content.value = v}
onSubmit={(nextContent) => {
onRequest({
type: 'user',
content: nextContent,
});
content.value = '';
}}
/>
</Flex>
)
});
</script>
隐藏源代码
API
tsx
type useXChat<AgentMessage, ParsedMessage = AgentMessage> = (
config: XChatConfig<AgentMessage, ParsedMessage>,
) => XChatConfigReturnType;
XChatConfig
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
agent | 通过 useXAgent 生成的 agent ,当使用 onRequest 方法时, agent 参数是必需的。 | XAgent | - | |
defaultMessages | 默认展示信息 | { status, message }[] | - | |
parser | 将 AgentMessage 转换成消费使用的 ParsedMessage,不设置时则直接消费 AgentMessage。支持将一条 AgentMessage 转换成多条 ParsedMessage | (message: AgentMessage) => BubbleMessage | BubbleMessage[] | - | |
requestFallback | 请求失败的兜底信息,不提供则不会展示 | AgentMessage | () => AgentMessage | - | |
requestPlaceholder | 请求中的占位信息,不提供则不会展示 | AgentMessage | () => AgentMessage | - |
XChatConfigReturnType
属性 | 说明 | 类型 | 版本 |
---|---|---|---|
messages | 当前管理的内容 | AgentMessages[] | |
parsedMessages | 经过 parser 转译过的内容 | ParsedMessages[] | |
onRequest | 添加一条 Message,并且触发请求 | (message) => void | |
setMessages | 直接修改 messages,不会触发请求 | (messages: { message, status }[]) => void |