Bubble 对话气泡
用于聊天的气泡组件。
何时使用
常用于聊天的时候。
代码演示
基本
基础用法。
h
ts
<script setup lang="tsx">
import { Bubble } from 'ant-design-x-vue';
defineOptions({ name: 'AXBubbleBasic' });
defineRender(() => {
return (
<Bubble content="hello world !" />
)
})
</script>
隐藏源代码
支持位置和头像
通过 avatar
设置自定义头像,通过 placement
设置位置,提供了 start
、end
两个选项。
G
W
H
T
ts
<script setup lang="tsx">
import { Bubble } from 'ant-design-x-vue';
import { UserOutlined } from '@ant-design/icons-vue';
import { Flex } from 'ant-design-vue';
import type { CSSProperties } from 'vue';
defineOptions({ name: 'BubbleAvatarAndPlacement' });
const fooAvatar: CSSProperties = {
color: '#f56a00',
backgroundColor: '#fde3cf',
};
const barAvatar: CSSProperties = {
color: '#fff',
backgroundColor: '#87d068',
};
const hideAvatar: CSSProperties = {
visibility: 'hidden',
};
defineRender(() => {
return (
<Flex gap="middle" vertical>
<Bubble
placement="start"
content="Good morning, how are you?"
avatar={{ icon: <UserOutlined />, style: fooAvatar }}
/>
<Bubble
placement="start"
content="What a beautiful day!"
styles={{ avatar: hideAvatar }}
avatar={{}}
/>
<Bubble
placement="end"
content="Hi, good morning, I'm fine!"
avatar={{ icon: <UserOutlined />, style: barAvatar }}
/>
<Bubble placement="end" content="Thank you!" styles={{ avatar: hideAvatar }} avatar={{}} />
</Flex>
)
});
</script>
隐藏源代码
头和尾
通过 header
和 footer
属性设置气泡的头部和底部。
Ant Design X
H
ts
<script setup lang="tsx">
import { CopyOutlined, SyncOutlined, UserOutlined } from '@ant-design/icons-vue';
import { Bubble } from 'ant-design-x-vue';
import { Button, Space, theme } from 'ant-design-vue';
defineOptions({ name: 'BubbleHeaderAndFooter' });
const { token } = theme.useToken();
defineRender(() => {
return (
<Bubble
content="Hello, welcome to use Ant Design X! Just ask if you have any questions."
avatar={{ icon: <UserOutlined /> }}
header="Ant Design X"
footer={
<Space size={token.value.paddingXXS}>
<Button type="text" size="small" icon={<SyncOutlined />} />
<Button type="text" size="small" icon={<CopyOutlined />} />
</Space>
}
/>
)
});
</script>
隐藏源代码
加载中
通过 loading
属性控制加载状态。
Loading state:
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Flex, Switch } from 'ant-design-vue';
import { Bubble } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'BubbleLoading' });
const loading = ref(true);
defineRender(() => {
return (
<Flex gap="large" vertical>
<Bubble loading={loading.value} content="hello world !" avatar={{ icon: <UserOutlined /> }} />
<Flex gap="large" wrap="wrap">
Loading state:
<Switch v-model:checked={loading.value} />
</Flex>
</Flex>
)
});
</script>
隐藏源代码
打字效果
通过设置 typing
属性,开启打字效果。更新 content
如果是之前的子集,则会继续输出,否则会重新输出。
A
A💗
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Bubble } from 'ant-design-x-vue';
import { Button, Flex } from 'ant-design-vue';
import { ref } from 'vue';
defineOptions({ name: 'BubbleTyping' });
const text = ref('Ant Design X love you! ');
const repeat = ref(1);
defineRender(() => {
return (
<Flex vertical gap="small">
<Bubble
content={text.value.repeat(repeat.value)}
typing={{ step: 2, interval: 50 }}
avatar={{ icon: <UserOutlined /> }}
/>
<Bubble
content={text.value.repeat(repeat.value)}
typing={{ step: 2, interval: 50, suffix: <>💗</> }}
avatar={{ icon: <UserOutlined /> }}
/>
<Button
style={{ alignSelf: 'flex-end' }}
onClick={() => {
repeat.value = (repeat.value < 5 ? repeat.value + 1 : 1);
}}
>
Repeat {repeat.value} Times
</Button>
</Flex>
)
});
</script>
隐藏源代码
自定义渲染
配合 markdown-it
实现自定义渲染内容。
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Bubble } from 'ant-design-x-vue';
import type { BubbleProps } from 'ant-design-x-vue';
import { Typography } from 'ant-design-vue';
import markdownit from 'markdown-it';
import { onWatcherCleanup, ref, watchEffect } from 'vue';
defineOptions({ name: 'BubbleMarkdown' });
const md = markdownit({ html: true, breaks: true });
const text = `
> Render as markdown content to show rich text!
Link: [Ant Design X](https://x.ant.design)
`.trim();
const renderMarkdown: BubbleProps['messageRender'] = (content) => (
<Typography>
<div v-html={md.render(content)} />
</Typography>
);
const renderKey = ref(0);
watchEffect(() => {
const id = setTimeout(
() => {
renderKey.value = renderKey.value + 1;
},
text.length * 100 + 2000,
);
onWatcherCleanup(() => {
clearTimeout(id);
});
});
defineRender(() => {
return (
<div style={{ height: 100 }} key={renderKey.value}>
<Bubble
typing
content={text}
messageRender={renderMarkdown}
avatar={{ icon: <UserOutlined /> }}
/>
</div>
)
});
</script>
隐藏源代码
变体
通过 variant
属性设置气泡的样式变体。
v
v
v
waiting for support
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { Bubble } from 'ant-design-x-vue';
import { Flex } from 'ant-design-vue';
defineOptions({ name: 'BubbleVariant' });
// const items: PromptsProps['items'] = [
// {
// key: '6',
// icon: <CoffeeOutlined style={{ color: '#964B00' }} />,
// description: 'How to rest effectively after long hours of work?',
// },
// {
// key: '7',
// icon: <SmileOutlined style={{ color: '#FAAD14' }} />,
// description: 'What are the secrets to maintaining a positive mindset?',
// },
// {
// key: '8',
// icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
// description: 'How to stay calm under immense pressure?',
// },
// ];
defineRender(() => {
return (
<Flex vertical gap="middle">
<Bubble variant="filled" avatar={{ icon: <UserOutlined /> }} content="variant: filled" />
<Bubble variant="outlined" avatar={{ icon: <UserOutlined /> }} content="variant: outlined" />
<Bubble variant="shadow" avatar={{ icon: <UserOutlined /> }} content="variant: shadow" />
<Bubble
variant="borderless"
avatar={{ icon: <UserOutlined /> }}
content={<div>waiting for support</div>}
// content={<Prompts title="variant: borderless to customize" items={items} vertical />}
/>
</Flex>
)
});
</script>
隐藏源代码
形状
通过 shape
属性设置气泡的形状。
s
T
s
T
s
T
ts
<script setup lang="tsx">
import { Bubble, type BubbleProps } from 'ant-design-x-vue';
import { Flex } from 'ant-design-vue';
defineOptions({ name: 'BubbleShape' });
const sharedLongTextProps: BubbleProps = {
placement: 'end',
content:
'This is a long text message to show the multiline view of the bubble component. '.repeat(3),
styles: { content: { maxWidth: 500 } },
};
defineRender(() => {
return (
<Flex gap="middle" vertical>
<Bubble content="shape: default" />
<Bubble {...sharedLongTextProps} />
<Bubble content="shape: round" shape="round" />
<Bubble {...sharedLongTextProps} shape="round" />
<Bubble content="shape: corner" shape="corner" />
<Bubble {...sharedLongTextProps} shape="corner" />
</Flex>
)
});
</script>
隐藏源代码
气泡列表
预设样式的气泡列表,支持自动滚动。使用 roles
设置气泡默认属性。
M
M
M
ts
<script setup lang="tsx">
import { UserOutlined } from '@ant-design/icons-vue';
import { BubbleList } from 'ant-design-x-vue';
import { Button, Flex } from 'ant-design-vue';
import type { BubbleListProps } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'BubbleList' });
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
typing: { step: 5, interval: 20 },
style: {
maxWidth: '600px',
},
},
user: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
const count = ref<number>(3);
// const listRef = useTemplateRef<InstanceType<typeof BubbleList>>(null);
const listRef = ref<InstanceType<typeof BubbleList>>(null);
defineRender(() => {
return (
<Flex vertical gap="small">
<Flex gap="small" style={{ alignSelf: 'flex-end' }}>
<Button
onClick={() => {
count.value = count.value + 1;
}}
>
Add Bubble
</Button>
<Button
onClick={() => {
listRef.value?.scrollTo({ key: 0, block: 'nearest' });
}}
>
Scroll To First
</Button>
</Flex>
<BubbleList
ref={listRef}
style={{ maxHeight: '300px' }}
roles={roles}
items={Array.from({ length: count.value }).map((_, i) => {
const isAI = !!(i % 2);
const content = isAI ? 'Mock AI content. '.repeat(20) : 'Mock user content.';
return {
key: i,
role: isAI ? 'ai' : 'user',
content,
};
})}
/>
</Flex>
)
});
</script>
隐藏源代码
语义化自定义
示例通过语义化以及加载定制,来调整气泡效果。
M
M
Custom loading...
ts
<script setup lang="tsx">
import { FrownOutlined, SmileOutlined, SyncOutlined, UserOutlined } from '@ant-design/icons-vue';
import { BubbleList } from 'ant-design-x-vue';
import { Button, Flex, Space, Spin } from 'ant-design-vue';
import type { BubbleListProps } from 'ant-design-x-vue';
import { ref } from 'vue';
defineOptions({ name: 'BubbleCustom' });
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
typing: { step: 5, interval: 20 },
style: {
maxWidth: 600,
marginInlineEnd: 44,
},
styles: {
footer: {
width: '100%',
},
},
loadingRender: () => (
<Space>
<Spin size="small" />
Custom loading...
</Space>
),
},
user: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
// const listRef = useTemplateRef<InstanceType<typeof BubbleList>>(null);
const listRef = ref<InstanceType<typeof BubbleList>>(null);
defineRender(() => {
return (
<BubbleList
ref={listRef}
style={{ maxHeight: 300 }}
roles={roles}
items={[
{
key: 'welcome',
role: 'ai',
content: 'Mock welcome content. '.repeat(10),
footer: (
<Flex>
<Button
size="small"
type="text"
icon={<SyncOutlined />}
style={{ marginInlineEnd: 'auto' }}
/>
<Button size="small" type="text" icon={<SmileOutlined />} />
<Button size="small" type="text" icon={<FrownOutlined />} />
</Flex>
),
},
{
key: 'ask',
role: 'user',
content: 'Mock user content.',
},
{
key: 'ai',
role: 'ai',
loading: true,
},
]}
/>
)
});
</script>
隐藏源代码
自定义列表内容
自定义气泡列表内容,这对于个性化定制场景非常有用。
N
ReactNode message
waiting for support
waiting for support
ts
<script setup lang="tsx">
import { CoffeeOutlined, FireOutlined, SmileOutlined, UserOutlined } from '@ant-design/icons-vue';
import { BubbleList } from 'ant-design-x-vue';
import { Typography } from 'ant-design-vue';
import type { BubbleListProps } from 'ant-design-x-vue';
defineOptions({ name: 'BubbleListCustom' });
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
typing: true,
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
},
suggestion: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { visibility: 'hidden' } },
variant: 'borderless',
messageRender: () => <div>waiting for support</div>
// messageRender: (items) => <Prompts vertical items={items as any} />,
},
file: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { visibility: 'hidden' } },
variant: 'borderless',
messageRender: () => <div>waiting for support</div>
// messageRender: (items: any) => (
// <Flex vertical gap="middle">
// {(items as any[]).map((item) => (
// <Attachments.FileCard key={item.uid} item={item} />
// ))}
// </Flex>
// ),
},
};
defineRender(() => {
return (
<BubbleList
roles={roles}
items={[
// Normal
{
key: 0,
role: 'ai',
content: 'Normal message',
},
// ReactNode
{
key: 1,
role: 'ai',
content: <Typography.Text type="danger">ReactNode message</Typography.Text>,
},
// Role: suggestion
{
key: 2,
role: 'suggestion',
content: [
{
key: '6',
icon: <CoffeeOutlined style={{ color: '#964B00' }} />,
description: 'How to rest effectively after long hours of work?',
},
{
key: '7',
icon: <SmileOutlined style={{ color: '#FAAD14' }} />,
description: 'What are the secrets to maintaining a positive mindset?',
},
{
key: '8',
icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
description: 'How to stay calm under immense pressure?',
},
],
},
// Role: file
{
key: 3,
role: 'file',
content: [
{
uid: '1',
name: 'excel-file.xlsx',
size: 111111,
description: 'Checking the data',
},
{
uid: '2',
name: 'word-file.docx',
size: 222222,
status: 'uploading',
percent: 23,
},
],
},
]}
/>
)
});
</script>
隐藏源代码
使用 GPT-Vis 渲染图表 (no support)
@antv/GPT-Vis 仅支持React。
配合 @antv/GPT-Vis 实现大模型输出的图表渲染,支持模型流式输出。
ts
<script setup lang="tsx">
import { ref } from 'vue';
import { Bubble } from 'ant-design-x-vue';
import type { BubbleProps } from 'ant-design-x-vue';
// import { GPTVis } from '@antv/gpt-vis';
import { Button, Flex } from 'ant-design-vue';
defineOptions({ name: 'BubbleGptVis' });
const text = `
**GPT-Vis**, Components for GPTs, generative AI, and LLM projects. Not only UI Components. [more...](https://github.com/antvis/GPT-Vis) \n\n
Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
\`\`\`vis-chart
{
"type": "line",
"data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}],
"axisXTitle": "year",
"axisYTitle": "sale"
}
\`\`\`
`;
const RenderMarkdown: BubbleProps['messageRender'] = (content) => {
// The @antv/gpt-vis is only support React
// return <GPTVis>{content}</GPTVis>;
return content
};
const rerenderKey = ref(0);
defineRender(() => {
return (
<Flex vertical gap="small" key={rerenderKey.value}>
<Button
style={{ alignSelf: 'flex-end' }}
onClick={() => {
rerenderKey.value = rerenderKey.value + 1
}}
>
Re-Render
</Button>
<Bubble
typing={{ step: 20, interval: 150 }}
content={text}
messageRender={RenderMarkdown}
avatar={{
src: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2Q5LRJ3LFPUAAAAAAAAAAAAADmJ7AQ/fmt.webp',
}}
variant="outlined"
/>
</Flex>
)
});
</script>
隐藏源代码
API
Bubble
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
avatar | 展示头像 | VNode | - | |
classNames | 语义化结构 class | Record<SemanticDOM, string> | - | |
content | 聊天内容 | string | - | |
footer | 底部内容 | VNode | - | |
header | 头部内容 | VNode | - | |
loading | 聊天内容加载状态 | boolean | - | |
placement | 信息位置 | start | end | start | |
shape | 气泡形状 | round | corner | - | |
styles | 语义化结构 style | Record<SemanticDOM, CSSProperties> | - | |
typing | 设置聊天内容打字动画 | boolean | { step?: number, interval?: number } | false | |
variant | 气泡样式变体 | filled | borderless | outlined | shadow | filled | |
loadingRender | 自定义渲染加载态内容 | () => VNode | - | |
messageRender | 自定义渲染内容 | (content?: string) => VNode | - | |
onTypingComplete | 打字效果完成时的回调,如果没有设置 typing 将在渲染时立刻触发 | () => void | - |
Bubble.List
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
autoScroll | 当内容更新时,自动滚动到最新位置。如果用户滚动,则会暂停自动滚动。 | boolean | true | |
items | 气泡数据列表 | (BubbleProps & { key?: string | number, role?: string })[] | - | |
roles | 设置气泡默认属性,items 中的 role 会进行自动对应 | Record<string, BubbleProps> | (bubble) => BubbleProps | - |