Plus 组件

对话

用于快速搭建对话内容

使用场景

Chat 组件可用于普通会话,AI 会话等场景。

对话内容渲染基于 MarkdownRender 组件,支持 Markdown 和 MDX (注:Chat 中的 MarkdownRender 的默认 format 模式是 md,如果需要使用 MDX 格式,可通过 markdownRenderProps API 设置),可实现图片,表格,链接,加粗,代码区等常用富文本功能。更加复杂的定制化需求通过作用域插槽实现。代码区的高亮语言需先通过 CodeHighlight 的 CodeHighlight.register 注册。

代码演示

如何引入

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

基本用法

通过 v-model:chats 绑定对话列表,@messageSend 监听发送实现基础对话显示和交互。v-model:chats 在列表变化时先触发 update:chats,再触发 chatsChange;只用单向的 :chats 而不回写时,列表保持不变。

附件支持通过点击上传按钮,输入框粘贴,拖拽文件至 Chat 区域上传。通过 uploadProps 设置上传参数,详情参考 Upload。

上传按钮的提示文案通过 #uploadTip 插槽设置,Tooltip 的其他属性通过 uploadTipProps 设置(不含 content),详情参考 Tooltip。

对话是多方参与,多轮交互的场景。可通过 roleConfig 传入角色信息(包括名称,头像等),具体参数细节 RoleConfig。

使用 align 属性可以设置对话的布局,支持左右分布(leftRight, 默认)和左对齐(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;
代码
<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>

消息状态

chats 类型为 Message[], Message 包含对话的各种信息,如角色(role)、内容(content)、附件(attachment)、状态(status)、唯一标识(id)、创建时间(createAt)等,具体见 Message。其中 status 不同,会话样式不同。

Assistant

请求成功

请求错误

代码
<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>

动态更新数据

对于后台返回 Serve Side Event 数据情况,可将获取到的数据用于更新 chats,对话内容将实时更新。

showStopGenerate 参数可用于设置是否展示停止生成按钮,默认为 false。 可以在 @stopGenerator 中处理停止生成逻辑。

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 设计稿转代码的演进之路 - 知乎
代码
<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>

清除上下文

通过 showClearContext 可以开启在输入框中显示清除上下文按钮,默认为 false。也可以通过模板 ref 调用 clearContext 方法清除上下文。

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

代码
<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>

自定义渲染会话框

通过作用域插槽自定义会话框的各个部分,插槽参数类型如下。插槽参数里的 class 是该部分默认的类名,自定义的根元素需要绑定它。

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

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

/* 用于没有图片 URL 的角色,渲染头像里的内容 */
export interface ChatRoleAvatarSlotProps {
    message: ChatMessage;
    role: ChatMetadata;
}

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

/* 与默认操作栏显示的按钮一致,按钮不显示时(如消息未完成)对应的键不存在 */
export interface ChatDefaultActionNodeObj {
    copyNode?: VNode;
    likeNode?: VNode;
    dislikeNode?: VNode;
    resetNode?: VNode;
    deleteNode?: VNode;
}

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

/* 每个部分都是消息默认渲染的结果,已包含 avatar、title、content、action 插槽的自定义 */
export interface ChatFullChatBoxNodes {
    avatar: VNode;
    title: VNode;
    content: VNode;
    action: VNode;
}

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

自定义渲染头像和标题,可通过 #avatar 和 #title 插槽实现。提供插槽但内容为空时,对应的头像或标题不显示;roleConfig 的 avatar 只接受图片 URL,需要节点头像时使用 #roleAvatar 插槽。

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

Hello, I'm your AI assistant.

这张图片里有什么?

图片

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

代码
<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>

鼠标移动到会话上,即可显示会话操作区,通过 #action 插槽自定义渲染操作区

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

代码
<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>

通过 #content 插槽自定义内容区域

Assistant
代码
<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>

使用 #fullChatBox 插槽自定义渲染整个会话框

System

Hello, I'm your AI assistant.

介绍一下 semi design

Assistant

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

代码
<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>

自定义渲染输入框

可通过 #inputArea 插槽自定义渲染输入框,插槽参数如下

export interface ChatInputAreaSlotProps {
    /* 默认节点 */
    defaultNode: VNode;
    /* 清除上下文按钮 */
    clearContextNode: VNode;
    /* 上传按钮,上传方式全部关闭时为 null */
    uploadNode: VNode | null;
    /* 文本输入框 */
    inputNode: VNode;
    /* 发送按钮 */
    sendNode: VNode;
    /* 如果自定义输入框,发送消息时需调用 */
    onSend: (content?: string, attachment?: FileItem[]) => void;
    /* 如果自定义清除上下文按钮,点击清除上下文时需调用 */
    onClear: () => void;
    /* 点击触发聚焦文本输入框的处理函数 */
    onClick: () => void;
}

各部分节点的使用示例如下

<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>

其他使用示例如下

System

Hello, I'm your AI assistant.

输入信息
代码
<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>

提示信息

通过 hints 可设置提示区域内容, 点击提示内容后,提示内容将成为新的用户输入内容,并触发 hintClick 事件。

Assistant

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

代码
<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>

自定义提示信息渲染

通过 #hintBox 插槽自定义提示区域内容, 参数如下

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

使用示例如下:

Assistant

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

代码
<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 参考

Props

属性说明类型默认值
chats对话列表,配合 v-model:chats 使用。只有整体替换数组才视为变化,就地 push 不会触发跟随滚动。Message[]semiGlobalDefault("Chat", "chats")
align对话布局方式,可选 leftRight(左右分布)或 leftAlign(左对齐)。ChatAlignsemiDefault("Chat", "align", "leftRight")
mode对话模式,可选 bubble、noBubble 或 userBubble。ChatModesemiDefault("Chat", "mode", "bubble")
roleConfig角色信息,键为消息的 role。avatar 只接受图片 URL,节点头像使用 roleAvatar 插槽;color 传给 Avatar。ChatRoleConfigsemiGlobalDefault("Chat", "roleConfig")
hints提示信息列表。点击某项后触发 hintClick,并将该项作为新的用户消息。string[]semiGlobalDefault("Chat", "hints")
sendHotKey发送快捷键,可选 enter 或 shift+enter。enter 时单独按 Enter 发送,Shift+Enter 换行;shift+enter 相反。SendHotKeysemiDefault("Chat", "sendHotKey", "enter")
placeholder输入框占位符。stringsemiGlobalDefault("Chat", "placeholder")
showStopGenerate是否在最后一条消息生成中时显示停止生成按钮。生成中发送按钮不可用,点击停止按钮触发 stopGenerator。booleansemiDefault("Chat", "showStopGenerate", false)
showClearContext是否在输入框中显示清除上下文按钮。booleansemiDefault("Chat", "showClearContext", false)
enableUpload是否启用上传。布尔值同时控制粘贴、点击和拖拽上传;传对象时分别设置 pasteUpload、clickUpload、dragUpload,未设置的项为 true。boolean | EnableUploadPropssemiDefault("Chat", "enableUpload", true)
canSend发送按钮是否可用。通常由内部逻辑决定,设置后以此值为准。booleansemiGlobalDefault("Chat", "canSend")
escapeHtml是否转义用户消息中的 HTML 标签,防止被 Markdown 解析器当作 HTML 处理导致内容丢失。booleansemiDefault("Chat", "escapeHtml", true)
uploadProps传给 Upload 的属性,fileList 与 defaultFileList 由 Chat 管理,Upload 的事件以 onXxx 形式传入。添加或移除附件时 Chat 自己处理 onChange。ChatUploadPropssemiGlobalDefault("Chat", "uploadProps")
uploadTipProps上传按钮提示 Tooltip 的属性,不含 content;提示文字使用 uploadTip 插槽。TypeOmit<TooltipProps, "content">semiGlobalDefault("Chat", "uploadTipProps")
markdownRenderProps传给消息内容所用 MarkdownRender 的属性,默认 format 为 md。raw 与 components 不可设置,自定义渲染组件使用 customMarkDownComponents。ChatMarkdownRenderPropssemiGlobalDefault("Chat", "markdownRenderProps")
customMarkDownComponents自定义 Markdown 渲染组件,键为元素名,传给消息内容的 MarkdownRender。MarkdownRenderComponentssemiGlobalDefault("Chat", "customMarkDownComponents")
inputBoxCls输入框的类名。stringsemiGlobalDefault("Chat", "inputBoxCls")
inputBoxStyle输入框的样式。StyleValuesemiGlobalDefault("Chat", "inputBoxStyle")
hintCls提示区最外层的类名。stringsemiGlobalDefault("Chat", "hintCls")
hintStyle提示区最外层的样式。StyleValuesemiGlobalDefault("Chat", "hintStyle")

Events

事件说明回调参数
update:chats对话列表变化时触发,供 v-model:chats 回写;先于 chatsChange。[chats: Message[]]
chatsChange对话列表变化时触发。[chats: Message[]]
messageSend发送消息时触发,参数为文本内容和附件。[content: string, attachment?: FileItem[] | undefined]
messageCopy复制消息时触发。[message: Message]
messageGoodFeedback消息正向反馈时触发。[message: Message]
messageBadFeedback消息负向反馈时触发。[message: Message]
messageReset重置消息时触发;由 resetMessage 方法触发时参数为 null。[message: Message | null]
messageDelete删除消息时触发。[message: Message]
clear清除上下文时触发。[]
hintClick点击提示信息时触发。[hint: string]
inputChange输入内容或附件变化时触发,参数为 { inputValue, attachment }。[props: ChatInputChangeProps]
stopGenerator点击停止生成按钮时触发。[event: MouseEvent]

Slots

插槽说明作用域参数
topSlot对话区域顶部的内容。any
bottomSlot输入框下方的内容。any
title自定义消息标题,参数为 message、role 和默认标题 defaultTitle。提供但内容为空时不显示标题。ChatTitleSlotProps
avatar自定义消息头像,参数为 message、role 和默认头像 defaultAvatar。提供但内容为空时不显示头像。ChatAvatarSlotProps
roleAvatar没有图片 URL 的角色的头像内容,参数为 message 和 role。ChatRoleAvatarSlotProps
content自定义消息内容区域,参数为 message、role、默认内容 defaultContent 和类名 class。ChatContentSlotProps
action自定义消息操作栏,参数为 message、默认按钮数组 defaultActions、按名称索引的 defaultActionsObj 和类名 class。ChatActionSlotProps
fullChatBox完全自定义整个消息框,参数为 message、role、各部分默认节点 defaultNodes 和类名 class。ChatFullChatBoxSlotProps
inputArea自定义输入区域,参数为 defaultNode、各部分节点(clearContextNode、uploadNode、inputNode、sendNode)以及需要自行调用的 onSend、onClear、onClick。ChatInputAreaSlotProps
hintBox自定义提示项,参数为 content、index 和点击处理函数 onHintClick。ChatHintBoxSlotProps
divider自定义清除上下文后的分割线,参数为 message。ChatDividerSlotProps
uploadButton替换上传按钮。any
uploadTip上传按钮的提示文字。any

Exposes

名称说明类型
resetMessage将最后一条消息重置为空内容的 loading 状态并触发 messageReset,参数为 null。Type() => void
clearContext在对话末尾追加分割线以清除上下文并触发 clear;末尾已是分割线时无效果。Type() => void
scrollToBottom滚动到最底部,animation 为 true 时平滑滚动,否则直接跳转。Type(animation?: boolean | undefined) => void
sendMessage发送消息,不传 attachment 时消息的 content 为字符串。Type(content: string, attachment?: FileItem[] | undefined) => void

class、style 和其他未声明的属性落在根元素上,根元素带有 aria-label 或 aria-labelledby 且没有传 role 时,自动加上 role="group"。dragover、dragstart、dragend 监听同样落在根元素上,Chat 自己的拖拽上传处理先于使用方的监听执行。

RoleConfig

属性说明类型默认值
user用户信息ChatMetadata-
assistant助手信息ChatMetadata-
system系统信息ChatMetadata-

Metadata

属性说明类型默认值
name名称string-
avatar头像图片 URL。节点头像(如 Semi 图标)使用 #roleAvatar 插槽string-
color头像背景色,同 Avatar 组件的 color 参数, 支持 amber、 blue、 cyan、 green、 grey、 indigo、 light-blue、 light-green、 lime、 orange、 pink、 purple、 red、 teal、 violet、 yellowAvatarColorgrey

Message

属性说明类型默认值
role角色string-
name名称string-
id唯一标识string-
content文本内容string | Content[]-
parentId父节点idstring-
createAt创建时间number-
status消息状态,可选值为 loading | incomplete | complete | errorstringcomplete

Content

属性说明类型默认值
type类型, 可选值text | image_url | file_urlstring-
text当类型为 text 时的内容数据string-
image_url当类型为 image_url 时的内容数据{ url: string }-
file_url当类型为 file_url 时的内容数据{ url: string; name: string; size: string; type: string }-

Accessibility

键盘和焦点

  • 输入框中按 Enter 或 Shift + Enter 发送消息,由 sendHotKey 决定:enter 时 Enter 发送、Shift + Enter 换行,shift+enter 相反。
  • 在输入框按 Shift + Tab 回到上传按钮。默认的上传按钮是原生按钮,Enter 或 Space 打开文件选择;设置了上传提示时,提示文字是该按钮的描述,按钮获得焦点时显示。
  • 停止生成按钮仅在生成中出现。生成结束、按钮卸载时,如果焦点在它上面,焦点回到输入框,不会落到页面开头。
  • 除最后一条已完成的消息外,消息操作栏平时隐藏,仍在 Tab 顺序中,焦点进入其中的按钮时显示。删除按钮打开的确认框按 Esc 关闭,焦点回到删除按钮;在 Modal 中只关闭确认框,Modal 保持打开。
  • 消息列表是带名称的 role="log" 区域,最后一条消息生成中时带 aria-busy="true",读屏在回复完成后再播报,不逐段朗读。

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$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