Plus · Chat

Chat

Used to quickly build conversation content

When to use

The Chat component can be used in scenarios such as regular conversations or AI conversations.

The rendering of the conversation content is based on the MarkdownRender component, which supports Markdown and MDX (Note: The default format mode of MarkdownRender in Chat is md. If you need to use MDX format, you can set it through the markdownRenderProps API). It allows for common rich text features such as images, tables, links, bold text, code blocks, and more. More complex customization is achieved with scoped slots. Register the languages of code blocks with CodeHighlight.register of CodeHighlight to highlight them.

Demos

How to import

<script setup lang="ts">
    import { Chat } from '@fibrous/semi-ai-vue';
</script>

Basic usage

Bind the conversation list with v-model:chats and listen to @messageSend to achieve basic conversation display and interaction. When the list changes, v-model:chats first emits update:chats, then chatsChange; with a one-way :chats that is not written back, the list stays unchanged.

Attachments can be uploaded by clicking the upload button, pasting into the input box, or dragging files into the Chat area. Set the upload parameters through uploadProps. For details, refer to Upload.

The prompt text of the upload button is set through the #uploadTip slot, and the other properties of the Tooltip through uploadTipProps (without content). For details, please refer to Tooltip.

Dialogue is a scene involving multiple parties and multiple rounds of interaction. Role information (including name, avatar, etc.) can be passed in through roleConfig, and the specific parameter details are RoleConfig.

Use the align attribute to set the layout of the dialog, supporting left and right layout (leftRight, default) and left alignment (leftAlign).

模式
会话布局方式
System

Hello, I'm your AI assistant.

User

给一个 Semi Design 的 Button 组件的使用示例

Assistant

以下是一个 Semi 代码的使用示例:

jsx
import React from 'react';
import { Button } from '@fibrous/semi-ui-react';

const MyComponent = () => {
  return (
    <Button>Click me</Button>
 );
};
export default MyComponent;
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage, type ChatProps } from '@fibrous/semi-ai-vue';
    import { Radio, RadioGroup, type RadioValue } from '@fibrous/semi-ui-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        margin: '8px 16px',
        height: '550px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const message = ref<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: '给一个 Semi Design 的 Button 组件的使用示例',
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: "以下是一个 Semi 代码的使用示例:\n```jsx \nimport React from 'react';\nimport { Button } from '@fibrous/semi-ui-react';\n\nconst MyComponent = () => {\n  return (\n    <Button>Click me</Button>\n );\n};\nexport default MyComponent;\n```\n",
        },
    ]);
    const mode = ref<RadioValue | undefined>('bubble');
    const align = ref<RadioValue | undefined>('leftRight');

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            createAt: Date.now(),
            content: '这是一条 mock 回复信息',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };

    const onMessageReset = () => {
        setTimeout(() => {
            const lastMessage = message.value[message.value.length - 1];
            const newLastMessage: ChatMessage = {
                ...lastMessage,
                status: 'complete',
                content: 'This is a mock reset message.',
            };
            message.value = [...message.value.slice(0, -1), newLastMessage];
        }, 200);
    };
</script>

<template>
    <span style="display: flex; flex-direction: column; row-gap: 8px">
        <span style="display: flex; align-items: center; column-gap: 10px">
            模式
            <RadioGroup v-model="mode" type="button">
                <Radio value="bubble">气泡</Radio>
                <Radio value="noBubble">非气泡</Radio>
                <Radio value="userBubble">用户会话气泡</Radio>
            </RadioGroup>
        </span>
        <span style="display: flex; align-items: center; column-gap: 10px">
            会话布局方式
            <RadioGroup v-model="align" type="button">
                <Radio value="leftRight">左右分布</Radio>
                <Radio value="leftAlign">左对齐</Radio>
            </RadioGroup>
        </span>
    </span>
    <Chat :key="`${align}${mode}`" v-model:chats="message" :align="align as ChatProps['align']" :mode="mode as ChatProps['mode']" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageReset="onMessageReset" @messageSend="onMessageSend">
        <template #uploadTip>自定义上传按钮提示信息</template>
    </Chat>
</template>

Chat status

The chats type is Message[], where each Message contains various information about the conversation, such as role, content, attachment, status, unique identifier (id), creation time (createAt), and more. For detailed information, please refer to Message. The conversation style may vary depending on the different status values.

Assistant

请求成功

请求错误

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const messages = ref<Message[]>([
        {
            role: 'assistant',
            id: '1',
            createAt: 1715676751919,
            content: '请求成功',
        },
        {
            id: 'loading',
            role: 'assistant',
            status: 'loading',
        },
        {
            role: 'assistant',
            id: 'error',
            content: '请求错误',
            status: 'error',
        },
    ]);

    const onMessageSend = () => {
        const newAssistantMessage: Message = {
            role: 'assistant',
            id: getId(),
            createAt: Date.now(),
            content: '这是一条 mock 回复信息',
        };
        setTimeout(() => {
            messages.value = [...messages.value, newAssistantMessage];
        }, 200);
    };
</script>

<template>
    <Chat v-model:chats="messages" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend" />
</template>

Dynamic update chats

For the case of receiving Server-Sent Event data from the backend, the obtained data can be used to update the chats, and the conversation content will be updated in real-time.

The showStopGenerate parameter can be used to determine whether to display the stop generation button, with a default value of false. The logic for stopping the generation can be handled in the @stopGenerator listener.

System

Hello, I'm your AI assistant.

User

介绍一下 Semi design

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[1]。

Semi Design的特点包括:

  1. 设计简洁、现代化。
  2. 提供主题方案,可深度样式定制。
  3. 提供明暗色两套模式,切换方便。
  4. 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
  5. 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。

Learn more:

  1. Introduction 介绍 - Semi Design
  2. Getting Started 快速开始 - Semi Design
  3. Semi D2C 设计稿转代码的演进之路 - 知乎
Code
<script setup lang="ts">
    import { onBeforeUnmount, ref } from 'vue';
    import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '600px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const messages = ref<Message[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: '介绍一下 Semi design',
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: `
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[[1]](https://semi.design/zh-CN/start/introduction)。

Semi Design的特点包括:

1. 设计简洁、现代化。
2. 提供主题方案,可深度样式定制。
3. 提供明暗色两套模式,切换方便。
4. 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
5. 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。

---
Learn more:
1. [Introduction 介绍 - Semi Design](https://semi.design/zh-CN/start/introduction)
2. [Getting Started 快速开始 - Semi Design](https://semi.design/zh-CN/start/getting-started)
3. [Semi D2C 设计稿转代码的演进之路 - 知乎](https://zhuanlan.zhihu.com/p/667189184)
`,
        },
    ]);

    let timer: ReturnType<typeof setInterval> | null = null;

    const stopTimer = () => {
        if (timer) {
            clearInterval(timer);
            timer = null;
        }
    };

    const replaceLast = (update: (last: Message) => Message) => {
        messages.value = [...messages.value.slice(0, -1), update(messages.value[messages.value.length - 1])];
    };

    const generateMockResponse = (content: string) => {
        timer = setInterval(() => {
            const last = messages.value[messages.value.length - 1];
            if (last.status === 'loading') {
                replaceLast((m) => ({ ...m, content: `mock Response for ${content} \n`, status: 'incomplete' }));
            } else if (last.status === 'incomplete') {
                const next = `${last.content} mock stream message`;
                if ((last.content?.length ?? 0) > 200) {
                    stopTimer();
                    replaceLast((m) => ({ ...m, content: next, status: 'complete' }));
                } else {
                    replaceLast((m) => ({ ...m, content: next }));
                }
            }
        }, 400);
    };

    const onMessageSend = (content: string) => {
        messages.value = [...messages.value, { role: 'assistant', status: 'loading', createAt: Date.now(), id: getId() }];
        generateMockResponse(content);
    };

    const onStopGenerator = () => {
        if (!timer) {
            return;
        }
        stopTimer();
        const last = messages.value[messages.value.length - 1];
        if (last.status && last.status !== 'complete') {
            replaceLast((m) => ({ ...m, status: 'complete' }));
        }
    };

    onBeforeUnmount(stopTimer);
</script>

<template>
    <Chat v-model:chats="messages" :roleConfig="roleInfo" showStopGenerate :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend" @stopGenerator="onStopGenerator" />
</template>

Clear context

Displaying the clear context button in the input box can be enabled through showClearContext, which defaults to false. The context can also be cleared by calling the clearContext method through a template ref.

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        margin: '8px 16px',
        height: '550px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const message = ref<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: '介绍一下 semi design',
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
        },
    ]);

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            createAt: Date.now(),
            content: '这是一条 mock 回复信息',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };

    const onMessageReset = () => {
        setTimeout(() => {
            const lastMessage = message.value[message.value.length - 1];
            const newLastMessage: ChatMessage = {
                ...lastMessage,
                status: 'complete',
                content: 'This is a mock reset message.',
            };
            message.value = [...message.value.slice(0, -1), newLastMessage];
        }, 200);
    };
</script>

<template>
    <Chat v-model:chats="message" :roleConfig="roleInfo" showClearContext :style="commonOuterStyle" :uploadProps="uploadProps" @messageReset="onMessageReset" @messageSend="onMessageSend">
        <template #uploadTip>自定义上传按钮提示信息</template>
    </Chat>
</template>

Custom rendering dialog box

Each part of the dialog box is customized with a scoped slot. The slot parameter types are as follows. The class in the slot parameters is the default class name of that part; the custom root element needs to bind it.

export interface ChatTitleSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultTitle: VNode;
}

export interface ChatAvatarSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultAvatar: VNode;
}

/* For a role without an image URL, renders the content inside the avatar */
export interface ChatRoleAvatarSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
}

export interface ChatContentSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultContent: VNode;
    class: string;
}

/* The buttons the default action bar shows; a key is missing when its button is not shown (e.g. an unfinished message) */
export interface ChatDefaultActionNodeObj {
    copyNode?: VNode;
    likeNode?: VNode;
    dislikeNode?: VNode;
    resetNode?: VNode;
    deleteNode?: VNode;
}

export interface ChatActionSlotProps {
    message: ChatMessage;
    defaultActions: VNode[];
    defaultActionsObj: ChatDefaultActionNodeObj;
    class: string;
}

/* Each part as the message would render it, including what the avatar, title, content and action slots give */
export interface ChatFullChatBoxNodes {
    avatar: VNode;
    title: VNode;
    content: VNode;
    action: VNode;
}

export interface ChatFullChatBoxSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultNodes: ChatFullChatBoxNodes;
    class: string;
}

Custom render avatar and title through the #avatar and #title slots. A provided but empty slot hides the avatar or title; the avatar of roleConfig only accepts an image URL, so use the #roleAvatar slot for a node avatar.

头像渲染模式
标题渲染模式

Hello, I'm your AI assistant.

这张图片里有什么?

图片

图片中是一个有卡通画像装饰的黄色背包。

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
    import { Avatar, Radio, RadioGroup, type RadioValue } from '@fibrous/semi-ui-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const formatTime = (createAt = Date.now()) => {
        const date = new Date(createAt);
        return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
    };

    const message = ref<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: [
                {
                    type: 'text',
                    text: '这张图片里有什么?',
                },
                {
                    type: 'image_url',
                    image_url: {
                        url: '/assets/root-web-sites/edit-bag.jpeg',
                    },
                },
            ],
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: '图片中是一个有卡通画像装饰的黄色背包。',
        },
    ]);
    const title = ref<RadioValue | undefined>('null');
    const avatar = ref<RadioValue | undefined>('null');

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            content: 'This is a mock response',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };
</script>

<template>
    <span style="display: flex; flex-direction: column; row-gap: 8px; margin-bottom: 5px">
        <span style="display: flex; align-items: center; column-gap: 10px">
            头像渲染模式
            <RadioGroup v-model="avatar" type="button">
                <Radio value="default">默认头像</Radio>
                <Radio value="null">无头像</Radio>
                <Radio value="custom">自定义头像</Radio>
            </RadioGroup>
        </span>
        <span style="display: flex; align-items: center; column-gap: 10px">
            标题渲染模式
            <RadioGroup v-model="title" type="button">
                <Radio value="default">默认标题</Radio>
                <Radio value="null">无标题</Radio>
                <Radio value="custom">自定义标题</Radio>
            </RadioGroup>
        </span>
    </span>
    <Chat :key="`${avatar}${title}`" v-model:chats="message" class="component-chat-demo-custom-render" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
        <template v-if="avatar === 'custom'" #avatar="{ role }">
            <Avatar shape="square" size="extra-small" style="flex-shrink: 0">{{ role.name }}</Avatar>
        </template>
        <template v-else-if="avatar === 'null'" #avatar />
        <template v-if="title === 'custom'" #title="{ role, message: item }">
            <span class="title">
                {{ role.name }}
                <span class="time">{{ formatTime(item.createAt) }}</span>
            </span>
        </template>
        <template v-else-if="title === 'null'" #title />
    </Chat>
</template>

When hovering over a conversation, the conversation action area will be displayed. You can customize the rendering of the action area using the #action slot.

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
    import { IconForward, IconMoreStroked } from '@fibrous/semi-icons-vue';
    import { Button, Dropdown } from '@fibrous/semi-ui-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const message = ref<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: '介绍一下 semi design',
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
        },
    ]);

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            content: 'This is a mock response',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };
</script>

<template>
    <Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
        <template #action="{ defaultActions, class: actionClass }">
            <span :class="actionClass">
                <component :is="action" v-for="(action, index) in defaultActions" :key="index" />
                <Dropdown position="top" trigger="click">
                    <Button aria-label="更多" class="semi-chat-chatBox-action-btn" theme="borderless" type="tertiary">
                        <template #icon>
                            <IconMoreStroked />
                        </template>
                    </Button>
                    <template #content>
                        <Dropdown.Menu>
                            <Dropdown.Item>
                                <template #icon>
                                    <IconForward />
                                </template>
                                分享
                            </Dropdown.Item>
                        </Dropdown.Menu>
                    </template>
                </Dropdown>
            </span>
        </template>
    </Chat>
</template>

You can customize the content area using the #content slot.

Assistant
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
    import { IconChevronUp } from '@fibrous/semi-icons-vue';
    import { Avatar, AvatarGroup, MarkdownRender } from '@fibrous/semi-ui-vue';

    interface Source {
        avatar: string;
        url: string;
        title: string;
        subTitle: string;
        content: string;
    }

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '500px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const sourcesOf = (item: ChatMessage): Source[] | undefined => item.source;

    const message = ref<ChatMessage[]>([
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。它作为全面、易用、优质的现代应用 UI 解决方案,从字节跳动各业务线的复杂场景提炼而来,支撑近千计平台产品,服务内外部 10 万+ 用户。',
            source: [
                {
                    avatar: '/assets/placeholder/logo.svg',
                    url: '/zh-CN/start/introduction',
                    title: 'semi Design',
                    subTitle: 'Semi design website',
                    content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。',
                },
                {
                    avatar: '/assets/placeholder/logo.svg',
                    url: '/dsm/landing',
                    subTitle: 'Semi DSM website',
                    title: 'Semi 设计系统',
                    content: '从 Semi Design,到 Any Design 快速定义你的设计系统,并应用在设计稿和代码中',
                },
                {
                    avatar: '/assets/placeholder/logo.svg',
                    url: '/code/zh-CN/start/introduction',
                    subTitle: 'Semi D2C website',
                    title: '设计稿转代码',
                    content: 'Semi 设计稿转代码(Semi Design to Code,或简称 Semi D2C),是由抖音前端 Semi Design 团队推出的全新的提效工具',
                },
            ],
        },
    ]);
    // Only the first message carries sources, so one open state serves the whole list
    const collapsed = ref(true);

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            content: 'This is a mock response',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };

    const buttonReset = {
        border: 'none',
        background: 'var(--semi-color-tertiary-light-hover)',
        borderRadius: '16px',
        fontFamily: 'inherit',
        lineHeight: 'inherit',
    };
</script>

<template>
    <Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
        <template #content="{ message: item, class: contentClass }">
            <div :class="contentClass">
                <div
                    v-if="sourcesOf(item)"
                    :style="{
                        transition: collapsed ? 'height 0.4s ease, width 0.4s ease' : 'height 0.4s ease',
                        height: collapsed ? '30px' : '200px',
                        width: collapsed ? '190px' : '100%',
                        background: 'var(--semi-color-tertiary-light-hover)',
                        borderRadius: '16px',
                        boxSizing: 'border-box',
                        marginBottom: '10px',
                    }"
                >
                    <button
                        v-show="collapsed"
                        :style="{
                            ...buttonReset,
                            display: 'flex',
                            width: 'fit-content',
                            columnGap: '10px',
                            padding: '5px 10px',
                            cursor: 'pointer',
                            fontSize: '14px',
                            color: 'var(--semi-color-text-1)',
                        }"
                        type="button"
                        @click="collapsed = false"
                    >
                        <span>基于{{ sourcesOf(item)?.length }}个搜索来源</span>
                        <AvatarGroup size="extra-extra-small">
                            <Avatar v-for="(s, index) in sourcesOf(item)" :key="index" alt="" :src="s.avatar" />
                        </AvatarGroup>
                    </button>
                    <button
                        v-show="!collapsed"
                        :style="{
                            ...buttonReset,
                            width: '100%',
                            height: '100%',
                            boxSizing: 'border-box',
                            display: 'flex',
                            flexDirection: 'column',
                            padding: '12px',
                            textAlign: 'start',
                            fontSize: 'inherit',
                            color: 'inherit',
                        }"
                        type="button"
                        @click="collapsed = true"
                    >
                        <span style="display: flex; align-items: center; justify-content: space-between; padding: 5px 10px; column-gap: 10px; color: var(--semi-color-text-1)">
                            <span style="font-size: 14px; font-weight: 500">Source</span>
                            <IconChevronUp />
                        </span>
                        <span style="display: flex; flex-wrap: wrap; gap: 10px; overflow: scroll; padding: 5px 10px">
                            <span v-for="s in sourcesOf(item)" :key="s.url" style="display: flex; flex-direction: column; row-gap: 5px; flex-basis: 150px; flex-grow: 1; border: 1px solid var(--semi-color-border); border-radius: 12px; padding: 12px; font-size: 12px">
                                <span style="display: flex; column-gap: 5px; align-items: center">
                                    <Avatar alt="" shape="square" :src="s.avatar" style="width: 16px; height: 16px; flex-shrink: 0" />
                                    <span style="color: var(--semi-color-text-2); text-overflow: ellipsis">
                                        {{ s.title }}
                                    </span>
                                </span>
                                <span style="color: var(--semi-color-primary); font-size: 12px">{{ s.subTitle }}</span>
                                <span style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; text-overflow: ellipsis; overflow: hidden; color: var(--semi-color-text-2)">
                                    {{ s.content }}
                                </span>
                            </span>
                        </span>
                    </button>
                </div>
                <MarkdownRender :raw="typeof item.content === 'string' ? item.content : ''" />
            </div>
        </template>
    </Chat>
</template>

Use the #fullChatBox slot to custom render the entire chat box

System

Hello, I'm your AI assistant.

介绍一下 semi design

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
    import { Avatar } from '@fibrous/semi-ui-vue';

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    const titleStyle = {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        columnGap: '10px',
        padding: '5px 0px',
        width: 'fit-content',
    };

    let id = 0;
    const getId = () => `id-${id++}`;

    const message = ref<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
        {
            role: 'user',
            id: '2',
            createAt: 1715676751919,
            content: '介绍一下 semi design',
        },
        {
            role: 'assistant',
            id: '3',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
        },
    ]);

    const onMessageSend = () => {
        const newAssistantMessage: ChatMessage = {
            role: 'assistant',
            id: getId(),
            content: 'This is a mock response',
        };
        setTimeout(() => {
            message.value = [...message.value, newAssistantMessage];
        }, 200);
    };
</script>

<template>
    <Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
        <template #fullChatBox="{ role, message: item, defaultNodes, class: boxClass }">
            <div :class="boxClass">
                <div
                    :style="{
                        display: 'flex',
                        flexDirection: 'column',
                        rowGap: '4px',
                        alignItems: item.role === 'user' ? 'end' : undefined,
                    }"
                >
                    <span v-if="item.role !== 'user'" :style="titleStyle">
                        <Avatar alt="" shape="square" size="extra-small" :src="role.avatar" />
                        <component :is="defaultNodes.title" />
                    </span>
                    <div style="width: fit-content">
                        <component :is="defaultNodes.content" />
                    </div>
                    <component :is="defaultNodes.action" />
                </div>
            </div>
        </template>
    </Chat>
</template>

Custom render InputArea

The input box can be customized through the #inputArea slot, the slot parameters are as follows

export interface ChatInputAreaSlotProps {
    /* Default node */
    defaultNode: VNode;
    /* clear context button */
    clearContextNode: VNode;
    /* upload button, null when every upload way is off */
    uploadNode: VNode | null;
    /* text input area */
    inputNode: VNode;
    /* send button */
    sendNode: VNode;
    /* If you customize the input box, you need to call it when sending a message. */
    onSend: (content?: string, attachment?: FileItem[]) => void;
    /* If you customize the clear context button, it needs to be called when you click to clear the context */
    onClear: () => void;
    /* The processing function that triggers the focus of the text input box after clicking */
    onClick: () => void;
}

Example of the part nodes

<template>
    <Chat v-model:chats="chats">
        <template #inputArea="{ uploadNode, inputNode, sendNode, onClick }">
            <div
                style="
                    margin: 8px 16px;
                    display: flex;
                    flex-direction: row;
                    align-items: flex-end;
                    border-radius: 16px;
                    padding: 10px;
                    border: 1px solid var(--semi-color-border);
                "
                @click="onClick"
            >
                <component :is="uploadNode" />
                <component :is="inputNode" />
                <component :is="sendNode" />
            </div>
        </template>
    </Chat>
</template>

Other Example:

System

Hello, I'm your AI assistant.

输入信息
Code
<script setup lang="ts">
    import { ref, shallowRef } from 'vue';
    import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
    import { IconUpload } from '@fibrous/semi-icons-vue';
    import { Button, Form, FormInput, FormUpload, type FileItem, type FormApi } from '@fibrous/semi-ui-vue';

    interface InputValues {
        name?: string;
        file?: FileItem[];
    }

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '500px',
    };

    const inputStyle = {
        display: 'flex',
        flexDirection: 'column',
        border: '1px solid var(--semi-color-border)',
        margin: '8px 16px',
        borderRadius: '8px',
        padding: '8px',
    } as const;

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const messages = ref<Message[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
    ]);
    type Send = (content?: string, attachment?: FileItem[]) => void;

    const formApi = shallowRef<FormApi<InputValues>>();
    const setFormApi = (api: FormApi<InputValues>) => {
        formApi.value = api;
    };

    const onMessageSend = () => {
        const newAssistantMessage: Message = {
            role: 'assistant',
            id: getId(),
            content: `This is a mock response`,
        };
        setTimeout(() => {
            messages.value = [...messages.value, newAssistantMessage];
        }, 200);
    };

    const submit = (onSend: Send) => {
        const api = formApi.value;
        if (!api) {
            return;
        }
        const values = api.getValues();
        if (values.name?.length || values.file?.length) {
            onSend(values.name, values.file);
            api.reset();
        }
    };
</script>

<template>
    <Chat v-model:chats="messages" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
        <template #inputArea="area">
            <div :style="inputStyle">
                <Form @getFormApi="setFormApi">
                    <strong>输入信息</strong>
                    <FormInput field="name" label="名称(Input)" :style="{ width: '250px' }" trigger="blur" />
                    <FormUpload :action="uploadProps.action" field="file" label="文档">
                        <Button theme="light">
                            <template #icon>
                                <IconUpload />
                            </template>
                            点击上传
                        </Button>
                    </FormUpload>
                </Form>
                <Button :style="{ width: 'fit-content' }" @click="submit(area.onSend)">提交</Button>
            </div>
        </template>
    </Chat>
</template>

Hint

The prompt area content can be set through hints. After clicking the prompt content, the prompt content will become the new user input content and trigger the hintClick event.

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';

    const hintsExample = ['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?'];

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const messages = ref<Message[]>([
        {
            role: 'assistant',
            id: '1',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ]);
    const hints = ref(hintsExample);

    const onMessageSend = () => {
        const newAssistantMessage: Message = {
            role: 'assistant',
            id: getId(),
            createAt: Date.now(),
            content: '这是一条 mock 回复信息',
        };
        setTimeout(() => {
            messages.value = [...messages.value, newAssistantMessage];
        }, 200);
    };
</script>

<template>
    <Chat v-model:chats="messages" :hints="hints" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @clear="hints = []" @hintClick="hints = []" @messageSend="onMessageSend" />
</template>

Custom render Hint

Customize the content of the prompt area through the #hintBox slot, the parameters are as follows

export interface ChatHintBoxSlotProps {
    content: string;
    index: number;
    onHintClick: () => void;
}

Example:

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
    import { IconArrowRight } from '@fibrous/semi-icons-vue';

    const hintsExample = ['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?'];

    const roleInfo = {
        user: {
            name: 'User',
            avatar: '/assets/docs-icon.png',
        },
        assistant: {
            name: 'Assistant',
            avatar: '/assets/placeholder/logo.svg',
        },
        system: {
            name: 'System',
            avatar: '/assets/placeholder/logo.svg',
        },
    };

    const commonOuterStyle = {
        border: '1px solid var(--semi-color-border)',
        borderRadius: '16px',
        height: '400px',
    };

    const commonHintStyle = {
        border: '1px solid var(--semi-color-border)',
        padding: '10px',
        borderRadius: '10px',
        background: 'none',
        color: 'var( --semi-color-text-1)',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        textAlign: 'left',
        cursor: 'pointer',
        fontFamily: 'inherit',
        fontSize: '14px',
        lineHeight: 'inherit',
    } as const;

    const uploadProps = { action: 'https://api.semi.design/upload' };

    let id = 0;
    const getId = () => `id-${id++}`;

    const messages = ref<Message[]>([
        {
            role: 'assistant',
            id: '1',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ]);
    const hints = ref(hintsExample);

    const onMessageSend = () => {
        const newAssistantMessage: Message = {
            role: 'assistant',
            id: getId(),
            createAt: Date.now(),
            content: '这是一条 mock 回复信息',
        };
        setTimeout(() => {
            messages.value = [...messages.value, newAssistantMessage];
        }, 200);
        hints.value = [];
    };
</script>

<template>
    <Chat v-model:chats="messages" :hints="hints" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @clear="hints = []" @hintClick="hints = []" @messageSend="onMessageSend">
        <template #hintBox="{ content, onHintClick }">
            <button :style="commonHintStyle" type="button" @click="onHintClick">
                {{ content }}
                <IconArrowRight :style="{ marginLeft: '10px' }" />
            </button>
        </template>
    </Chat>
</template>

API Reference

Props

PropertyDescriptionTypeDefault
chatsThe conversation list, used with v-model:chats. Only replacing the whole array counts as a change; pushing into the same array does not trigger scroll following.Message[]semiGlobalDefault("Chat", "chats")
alignConversation layout: leftRight or leftAlign.ChatAlignsemiDefault("Chat", "align", "leftRight")
modeConversation mode: bubble, noBubble or userBubble.ChatModesemiDefault("Chat", "mode", "bubble")
roleConfigRole information, keyed by the message role. avatar takes an image URL only; use the roleAvatar slot for a node avatar. color is passed to Avatar.ChatRoleConfigsemiGlobalDefault("Chat", "roleConfig")
hintsThe list of hints. Clicking one emits hintClick and the hint becomes a new user message.string[]semiGlobalDefault("Chat", "hints")
sendHotKeySend hot key: enter or shift+enter. With enter, Enter sends and Shift+Enter inserts a line break; shift+enter is the reverse.SendHotKeysemiDefault("Chat", "sendHotKey", "enter")
placeholderPlaceholder of the input.stringsemiGlobalDefault("Chat", "placeholder")
showStopGenerateWhether to show the stop button while the last message is generating. Sending is unavailable meanwhile, and clicking the stop button emits stopGenerator.booleansemiDefault("Chat", "showStopGenerate", false)
showClearContextWhether to show the clear context button in the input.booleansemiDefault("Chat", "showClearContext", false)
enableUploadWhether uploading is enabled. A boolean controls paste, click and drag upload together; an object sets pasteUpload, clickUpload and dragUpload separately, and unset items are true.boolean | EnableUploadPropssemiDefault("Chat", "enableUpload", true)
canSendWhether the send button is usable. The internal logic decides by default; a given value overrides it.booleansemiGlobalDefault("Chat", "canSend")
escapeHtmlWhether to escape HTML tags in the user's messages, so the Markdown parser does not treat them as HTML and drop the content.booleansemiDefault("Chat", "escapeHtml", true)
uploadPropsProps passed to Upload. Chat owns fileList and defaultFileList, and Upload events are given as onXxx. Chat handles onChange itself when an attachment is added or removed.ChatUploadPropssemiGlobalDefault("Chat", "uploadProps")
uploadTipPropsProps of the upload button's Tooltip, without content; use the uploadTip slot for the text.TypeOmit<TooltipProps, "content">semiGlobalDefault("Chat", "uploadTipProps")
markdownRenderPropsProps passed to the MarkdownRender that renders message content; format defaults to md. raw and components cannot be set; use customMarkDownComponents for custom renderers.ChatMarkdownRenderPropssemiGlobalDefault("Chat", "markdownRenderProps")
customMarkDownComponentsCustom Markdown renderers keyed by element name, passed to the MarkdownRender of message content.MarkdownRenderComponentssemiGlobalDefault("Chat", "customMarkDownComponents")
inputBoxClsClass name of the input box.stringsemiGlobalDefault("Chat", "inputBoxCls")
inputBoxStyleStyle of the input box.StyleValuesemiGlobalDefault("Chat", "inputBoxStyle")
hintClsClass name of the outermost element of the hint area.stringsemiGlobalDefault("Chat", "hintCls")
hintStyleStyle of the outermost element of the hint area.StyleValuesemiGlobalDefault("Chat", "hintStyle")

Events

EventDescriptionCallback parameters
update:chatsEmitted when the conversation list changes, for v-model:chats to write back; fires before chatsChange.[chats: Message[]]
chatsChangeEmitted when the conversation list changes.[chats: Message[]]
messageSendEmitted when a message is sent, with the text and the attachments.[content: string, attachment?: FileItem[] | undefined]
messageCopyEmitted when a message is copied.[message: Message]
messageGoodFeedbackEmitted on positive feedback for a message.[message: Message]
messageBadFeedbackEmitted on negative feedback for a message.[message: Message]
messageResetEmitted when a message is reset; the argument is null when it comes from the resetMessage method.[message: Message | null]
messageDeleteEmitted when a message is deleted.[message: Message]
clearEmitted when the context is cleared.[]
hintClickEmitted when a hint is clicked.[hint: string]
inputChangeEmitted when the input text or the attachments change, with { inputValue, attachment }.[props: ChatInputChangeProps]
stopGeneratorEmitted when the stop button is clicked.[event: MouseEvent]

Slots

SlotDescriptionScope parameters
topSlotContent above the conversation area.any
bottomSlotContent below the input box.any
titleCustom message title, with message, role and the default title defaultTitle. Provided but empty, it hides the title.ChatTitleSlotProps
avatarCustom message avatar, with message, role and the default avatar defaultAvatar. Provided but empty, it hides the avatar.ChatAvatarSlotProps
roleAvatarAvatar content for a role without an image URL, with message and role.ChatRoleAvatarSlotProps
contentCustom message content area, with message, role, the default content defaultContent and the class name class.ChatContentSlotProps
actionCustom message action bar, with message, the default buttons defaultActions, defaultActionsObj keyed by name and the class name class.ChatActionSlotProps
fullChatBoxReplaces the whole message box, with message, role, the default node of each part defaultNodes and the class name class.ChatFullChatBoxSlotProps
inputAreaCustom input area, with defaultNode, the part nodes (clearContextNode, uploadNode, inputNode, sendNode) and onSend, onClear and onClick for you to call.ChatInputAreaSlotProps
hintBoxCustom hint item, with content, index and the click handler onHintClick.ChatHintBoxSlotProps
dividerCustom divider shown after the context is cleared, with message.ChatDividerSlotProps
uploadButtonReplaces the upload button.any
uploadTipText of the upload button's tooltip.any

Exposes

NameDescriptionType
resetMessageResets the last message to an empty loading one and emits messageReset with null.Type() => void
clearContextClears the context by appending a divider and emits clear; does nothing when the list already ends with one.Type() => void
scrollToBottomScrolls to the bottom; smoothly when animation is true, otherwise jumps.Type(animation?: boolean | undefined) => void
sendMessageSends a message; without attachment the message content is a string.Type(content: string, attachment?: FileItem[] | undefined) => void

class, style and the other undeclared attributes land on the root element. When the root has aria-label or aria-labelledby and no role, it gets role="group". The dragover, dragstart and dragend listeners also land on the root, and Chat's own drag-upload handling runs before the caller's listener.

RoleConfig

PROPERTIESINSTRUCTIONSTYPEDEFAULT
userUser informationChatMetadata-
assistantAssistant informationChatMetadata-
systemSystem informationChatMetadata-

Metadata

PROPERTIESINSTRUCTIONSTYPEDEFAULT
namenamestring-
avataravatar image URL. Use the #roleAvatar slot for a node avatar (such as Semi icons)string-
colorAvatar background color, same as the color parameter of Avatar component, support amber、 blue、 cyan、 green、 grey、 indigo、 light-blue、 light-green、 lime、 orange、 pink、 purple、 red、 teal、 violet、 yellowAvatarColorgrey

Message

PROPERTIESINSTRUCTIONSTYPEDEFAULT
rolerolestring-
namenamestring-
idUniquely identifiesstring-
contentall contentstring | Content[]-
parentIdparent Uniquely identifiesstring-
createAtcreation timenumber-
statusInformation status, loading | incomplete | complete | errorstringcomplete

Content

PROPERTIESINSTRUCTIONSTYPEDEFAULT
typetype, support text | image_url | file_urlstring-
textContent data when type is textstring-
image_urlContent data when type is image_url{ url: string }-
file_urlContent data when type is file_url{ url: string; name: string; size: string; type: string }-

Accessibility

Keyboard and Focus

  • In the input box, Enter or Shift + Enter sends the message, as decided by sendHotKey: with enter, Enter sends and Shift + Enter inserts a line break; shift+enter is the reverse.
  • Shift + Tab in the input box returns to the upload button. The default upload button is a native button: Enter or Space opens the file dialog. When an upload tip is set, its text is the description of that button and is shown while the button has focus.
  • The stop generation button only exists while generating. When generation ends and the button is unmounted, focus that was on it moves to the input box instead of falling to the start of the page.
  • Except on the last finished message, the message action bar stays hidden while remaining in the Tab order, and shows when focus enters one of its buttons. The confirmation opened by the delete button closes with Esc and focus returns to the delete button; inside a Modal only the confirmation closes and the Modal stays open.
  • The message list is a named role="log" region with aria-busy="true" while the last message is generating, so screen readers announce the reply once it completes rather than chunk by chunk.

Design Token

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$radius-chat_chatBox_contentvar(--semi-border-radius-large)聊天框内容圆角
$radius-chat_inputBox_container16px输入框容器圆角
$radius-chat_attachment_imgvar(--semi-border-radius-medium)附件图片圆角
$radius-chat_attachment_filevar(--semi-border-radius-medium)附件文件圆角
$radius-chat_hint_itemvar(--semi-border-radius-large)提示条圆角
$radius-chat_chatBox_content_codevar(--semi-border-radius-large)代码块圆角
$radius-chat_chatBox_content_code_topSlot_copyvar(--semi-border-radius-large)代码块顶部复制按钮圆角
$radius-chat_dropArea16px拖拽上传区域圆角
$color-chat_action_content-bgvar(--semi-color-bg-0)返回按钮/停止生成内容按钮背景颜色
$color-chat_action_content-bordervar(--semi-color-border)返回按钮/停止生成按钮描边颜色
$color-chat_dividervar(--semi-color-text-2)分割线文字颜色
$color-chat_chatBox_titlevar(--semi-color-text-0)聊天框标题颜色
$color-chat_chatBox_action_iconvar(--semi-color-text-2)聊天框操作区域按钮图标颜色
$color-chat_chatBox_action_icon-hovervar(--semi-color-text-0)聊天框操作区域按钮图标hover颜色
$color-chat_chatBox_action-bg-hovertransparent聊天框操作区域按钮hover背景颜色
$color-chat_chatBox_content_textvar(--semi-color-text-0)聊天框内容文字颜色
$color-chat_chatBox_content_bgvar(--semi-color-fill-0)聊天框内容背景颜色
$color-chat_chatBox_content_user-bgvar(--semi-color-primary)聊天框内容用户背景颜色
$color-chat_chatBox_content_user-textvar(--semi-color-white)聊天框内容用户文字颜色
$color-chat_chatBox_content_error-bgvar(--semi-color-danger-hover)聊天框内容错误背景颜色
$color-chat_chatBox_content_error-textvar(--semi-color-white)聊天框内容错误文字颜色
$color-chat_inputBottom_clearButton_iconvar(--semi-color-text-2)清空按钮图标颜色
$color-chat_inputBottom_uploadButton_iconvar(--semi-color-text-0)上传按钮图标颜色
$color-chat_inputBottom_sendButton_icon-disablevar(--semi-color-primary-disabled)发送按钮禁用态图标颜色
$color-chat_inputBox_container-bordervar(--semi-color-border)输入框容器边框颜色
$color-chat_attachment_clear_iconvar(--semi-color-text-2)附件清除图标颜色
$color-chat_attachment_file-bgvar(--semi-color-fill-0)附件文件背景颜色
$color-chat_attachment_failvar(--semi-color-danger)附件上传失败的提示图标颜色
$color-chat_chatBox_user_attachment_file-bgvar(--semi-color-bg-0)用户聊天框附件文件背景颜色
$color-chat_chatBox_other_attachment_file-bgvar(--semi-color-fill-2)聊天框附件文件背景颜色
$color-chat_attachment_file_iconvar(--semi-color-text-2)附件文件图标颜色
$color-chat_attachment_file_titlevar(--semi-color-text-0)附件文件标题颜色
$color-chat_attachment_file_metadata_textvar(--semi-color-text-2)附件文件元数据文字颜色
$color-chat_hint_item-bordervar(--semi-color-border)提示条边框颜色
$color-chat_hint_item-bgtransparent提示条背景颜色
$color-chat_hint_item-bg-hovervar(--semi-color-fill-0)提示条hover背景颜色
$color-chat_hint_content_textvar(--semi-color-text-1)提示条文字颜色
$color-chat_hint_iconvar(--semi-color-text-2)提示条图标颜色
$color-chat_chatBox_loading-bgvar(--semi-color-text-0)聊天内容加载图标圆圈颜色
$color-chat_chatBox_code_topSlotvar(--semi-white)代码块顶部字体颜色
$color-chat_chatBox_code_topSlot-bgvar(--semi-grey-4)代码块顶部背景色
$color-chat_chatBox_code_topSlot_toCopy-bg-hovervar(--semi-grey-5)代码块顶部复制按钮hover背景色
$color-chat_chatBox_code_contentvar(--semi-color-bg-0)代码块内容背景色
$color-chat_action_content-bg-hovervar(--semi-color-tertiary-light-hover)返回按钮/停止生成按钮hover背景颜色
$color-chat_dropArea-bgrgb(from var(--semi-grey-2) r g b / 0.9)拖拽区域文字颜色
$color-chat_dropArea-bordervar(--semi-color-border)拖拽区域边框颜色
$spacing-chat_paddingY12pxchat组件上下内边距
$spacing-chat_container-paddingX16px消息框水平内边距
$spacing-chat_action_content-bottom0返回按钮/停止生成按钮底部边距
$spacing-chat_chatBox-marginY8px聊天框上下外边距
$spacing-chat_chatBox-columnGap12px聊天框内容列间距
$spacing-chat_chatBox_action-columnGap10px聊天框操作区域按钮列间距
$spacing-chat_chatBox_action-marginX10px聊天框操作区域左右外边距
$spacing-chat_chatBox_action_btn-padding0聊天框操作区域按钮内边距
$spacing-chat_chatBox_content-paddingY8px聊天框内容上下内边距
$spacing-chat_chatBox_content-paddingX12px聊天框内容左右内边距
$spacing-chat_inputBox-paddingTop8px输入框顶部内边距
$spacing-chat_inputBox-paddingBottom8px输入框底部内边距
$spacing-chat_inputBox-paddingX16px输入框左右内边距
$spacing-chat_inputBox_container-padding11px输入框容器内边距
$spacing-chat_inputBox_inner-columnGap4px输入框容器列间距
$spacing-chat_inputBox-marginY4px
$spacing-chat_attachment-columnGap10px附件列间距
$spacing-chat_attachment-RowGap5px附件行间距
$spacing-chat_attachment-marginX12px附件左右外边距
$spacing-chat_attachment_clear-top8px附件清除图标顶部间距
$spacing-chat_attachment_clear-right8px附件清除图标右内边距
$spacing-chat_attachment_file-columnGap5px文件附件列间距
$spacing-chat_attachment_file-padding5px文件附件内边距
$spacing-chat_chatBox_loading_item-gap15px聊天内容加载图标间距
$spacing-chat_divider-marginY12px分割线上下外边距
$spacing-chat_chatBox_content_attachment-marginY4px聊天框内容文件/图片上下外间距
$spacing-chat_chatBox_content_attachment-marginRight4px聊天框内容文件/图片上下右间距
$spacing-chat_chatBox_content_code_topSlot-paddingX5px聊天框代码块顶部上下内边距
$spacing-chat_chatBox_content_code_topSlot-paddingY8px聊天框代码块顶部左右内边距
$spacing-chat_chatBox_content_code_topSlot_copy-columnGap5px聊天框代码块顶部复制按钮列间距
$spacing-chat_chatBox_content_code_topSlot_copy-padding5px聊天框代码块顶部复制按钮列间距
$spacing-chat_chatBox_wrap8px聊天框外层间距
$spacing-chat_hint-rowGap10px提示条行间距
$spacing-chat_hint-marginY12px提示条容器上下外边距
$spacing-chat_hint-marginLeft34px提示条容器左外边距
$spacing-chat_hint_item-marginY8px提示条上下外边距
$spacing-chat_hint_item-marginX12px提示条左右外边距
$spacing-chat_hint_item-columnGap20px提示条内容列间距
$spacing-chat_chatBox_loading-item-marginX18px聊天内容加载图标中心圆圈左右外边距
$spacing-chat_chatBox_loading-item-marginY6px聊天内容加载图标中心圆圈上下外边距
$width-chat_backBottom_wrapper42px返回按钮宽度
$width-chat_action_content-border1px返回按钮/停止生成按钮描边宽度
$width-chat_inputBottom_clearButton48px清空按钮宽度
$width-chat_inputBottom_uploadButton32px上传按钮宽度
$width-chat_inputBottom_sendButton32px发送按钮宽度
$width-chat_inputBox_container-border1px输入框容器边框宽度
$width-chat_hint_item-border1px提示条边框宽度
$width-chat_chatBox_loading8px加载中单个圆圈图标宽度
$width-chat_attachment_file_title90px附件文件标题最大宽度
$width-chat_max800pxchat组件最大宽度
$width-chat_dropArea-border5px拖拽上传边框宽度
$width-chat_chatBox_content_code_topSlot_copy150px聊天框代码块顶部复制按钮最小宽度
$width-chat_chatBox_avatar24px聊天框头像宽度
$height-chat_action_stop42px停止生成按钮高度
$font-chat_divider-fontWeight$font-weight-regular分割线字重
$font-chat_divider-fontSize$font-size-small分割线字体大小
$font-chat_chatBox_title-lineHeight20px聊天框标题行高
$font-chat_chatBox_title-fontSize$font-size-header-6聊天框标题字体大小
$font-chat_chatBox_title-fontWeight$font-weight-regular聊天框标题字重
$font-chat_inputBottom_clearButton_icon-fontSize30px输入区清空上下文按钮图标大小
$font-chat_attachment_file_title-fontSize$font-size-header-6附件文件标题字体大小
$font-chat_attachment_file_metadata-fontSize$font-size-regular附件文件元数据字体大小
$font-chat_hint_content-fontSize$font-size-regular提示条文字大小
$font-chat_chatBox_code_topSlot12px代码块顶部字体大小
$font-chat_chatBox_code_topSlot-lineHeight16px代码块顶部区域字体行高
$font-chat_dropArea_text48px拖拽上传区域文字大小
$z-chat_dropArea10拖拽上传区域z-index
$z-chat_action1返回按钮/停止生成按钮z-index