Plus 组件

对话

用于快速搭建对话内容

使用场景

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

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

代码演示

如何引入

<script lang="ts">
    import { Chat } from '@fibrae/semi-ai-svelte';
</script>

基本用法

通过 bind:chats 绑定对话列表,onMessageSend 监听发送实现基础对话显示和交互。发送消息、点赞、重置、清除上下文、删除时,Chat 先把新列表写回 chats,再触发 onChatsChange。列表整体替换、push 和最后一条消息文本的就地增长都会被跟随,其他就地修改(如深层 content 改动、单独翻转 status)会渲染但不被跟随,更新消息时建议整体替换数组。

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

上传按钮的提示文案通过 uploadTip snippet 设置,Tooltip 的其他属性通过 uploadTipProps 设置(不含 content),详情参考 Tooltip。上传按钮本身可用 uploadButton snippet 替换。uploadProps 不含 fileList 与 children,附件列表由 Chat 管理;未设置 action 时不会真正上传,需要自行提供 customRequest 或 action。

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

使用 align 属性可以设置对话的布局,支持左右分布(leftRight, 默认)和左对齐(leftAlign)。

模式
会话布局方式
System

Hello, I'm your AI assistant.

User

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

Assistant

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

svelte
<Button>Click me</Button>
代码
<script lang="ts">
    import { Chat, type ChatAlign, type ChatMessage, type ChatMode } from '@fibrae/semi-ai-svelte';
    import { Radio, RadioGroup, type RadioValue } from '@fibrae/semi-ui-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<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```svelte \n<Button>Click me</Button>\n```\n',
        },
    ]);
    let mode = $state<RadioValue | undefined>('bubble');
    let align = $state<RadioValue | undefined>('leftRight');

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

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

<span style="display: flex; flex-direction: column; row-gap: 8px">
    <span style="display: flex; align-items: center; column-gap: 10px">
        模式
        <RadioGroup aria-label="模式" bind:value={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 aria-label="会话布局方式" bind:value={align} type="button">
            <Radio value="leftRight">左右分布</Radio>
            <Radio value="leftAlign">左对齐</Radio>
        </RadioGroup>
    </span>
</span>
{#key `${align}${mode}`}
    <Chat align={align as ChatAlign} bind:chats={messages} mode={mode as ChatMode} {onMessageReset} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; margin: 8px 16px; height: 550px" {uploadProps}>
        {#snippet uploadTip()}自定义上传按钮提示信息{/snippet}
    </Chat>
{/key}

消息状态

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

Assistant

请求成功

请求错误

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

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

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

<Chat bind:chats={messages} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px" {uploadProps} />

动态更新数据

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

showStopGenerate 参数可用于设置是否展示停止生成按钮,默认为 false。可以在 onStopGenerator 中处理停止生成逻辑。流式更新消息时,每次用新对象替换最后一项并整体替换数组,Chat 会跟随更新。

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 lang="ts">
    import { onDestroy } from 'svelte';
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<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产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用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: ChatMessage) => ChatMessage) => {
        messages = [...messages.slice(0, -1), update(messages[messages.length - 1])];
    };

    const generateMockResponse = (content: string) => {
        timer = setInterval(() => {
            const last = messages[messages.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 = [...messages, { role: 'assistant', status: 'loading', createAt: Date.now(), id: getId() }];
        generateMockResponse(content);
    };

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

    onDestroy(stopTimer);
</script>

<Chat bind:chats={messages} {onMessageSend} {onStopGenerator} {roleConfig} showStopGenerate style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 600px" {uploadProps} />

清除上下文

通过 showClearContext 可以开启在输入框中显示清除上下文按钮,默认为 false。也可以通过组件实例(bind:this)调用 clearContext 方法清除上下文,实例还提供 resetMessage、scrollToBottom 和 sendMessage,见 API 的 Methods。清除后的分割线可用 divider snippet 自定义。

<script lang="ts">
    import { Chat, type ChatHandle } from '@fibrae/semi-ai-svelte';

    let chat = $state<ChatHandle>();
</script>

<Chat bind:this={chat} showClearContext />
<button onclick={() => chat?.clearContext()}>清除上下文</button>
System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<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(() => {
            messages = [...messages, newAssistantMessage];
        }, 200);
    };

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

<Chat bind:chats={messages} {onMessageReset} {onMessageSend} {roleConfig} showClearContext style="border: 1px solid var(--semi-color-border); border-radius: 16px; margin: 8px 16px; height: 550px" {uploadProps}>
    {#snippet uploadTip()}自定义上传按钮提示信息{/snippet}
</Chat>

自定义渲染会话框

通过同名 snippet 自定义会话框的各个部分,snippet 参数类型如下。参数里的 class 是该部分默认的类名,自定义的根元素需要绑定它;default* 是内置节点,类型是 snippet,在需要的位置用 {@render} 渲染。

import type { Snippet } from 'svelte';

export interface ChatTitleSnippetProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultTitle: Snippet;
}

export interface ChatAvatarSnippetProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultAvatar: Snippet;
}

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

export interface ChatContentSnippetProps {
    message: ChatMessage;
    role: ChatMetadata;
    defaultContent: Snippet;
    class: string;
}

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

export interface ChatActionSnippetProps {
    message: ChatMessage;
    defaultActions: Snippet[];
    defaultActionsObj: ChatDefaultActionNodeObj;
    class: string;
}

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

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

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

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

Hello, I'm your AI assistant.

这张图片里有什么?

图片

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

代码
<script lang="ts">
    import { Chat, type ChatAvatarSnippetProps, type ChatMessage, type ChatTitleSnippetProps } from '@fibrae/semi-ai-svelte';
    import { Avatar, Radio, RadioGroup, type RadioValue } from '@fibrae/semi-ui-svelte';

    const roleConfig = {
        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 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')}`;
    };

    let messages = $state<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: '图片中是一个有卡通画像装饰的黄色背包。',
        },
    ]);
    let titleMode = $state<RadioValue | undefined>('null');
    let avatarMode = $state<RadioValue | undefined>('null');

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

{#snippet customAvatar({ role }: ChatAvatarSnippetProps)}
    <Avatar shape="square" size="extra-small" style="flex-shrink: 0">{role.name}</Avatar>
{/snippet}

{#snippet customTitle({ role, message }: ChatTitleSnippetProps)}
    <span class="title">
        {role.name}
        <span class="time">{formatTime(message.createAt)}</span>
    </span>
{/snippet}

<!-- a provided snippet that renders nothing hides the avatar or the title -->
{#snippet nothing()}{/snippet}

<span style="display: flex; flex-direction: column; row-gap: 8px; margin-bottom: 5px">
    <span style="display: flex; align-items: center; column-gap: 10px">
        头像渲染模式
        <RadioGroup aria-label="头像渲染模式" bind:value={avatarMode} 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 aria-label="标题渲染模式" bind:value={titleMode} type="button">
            <Radio value="default">默认标题</Radio>
            <Radio value="null">无标题</Radio>
            <Radio value="custom">自定义标题</Radio>
        </RadioGroup>
    </span>
</span>
{#key `${avatarMode}${titleMode}`}
    <Chat
        avatar={avatarMode === 'custom' ? customAvatar : avatarMode === 'null' ? nothing : undefined}
        bind:chats={messages}
        class="component-chat-demo-custom-render"
        {onMessageSend}
        {roleConfig}
        style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px"
        title={titleMode === 'custom' ? customTitle : titleMode === 'null' ? nothing : undefined}
        {uploadProps}
    />
{/key}

鼠标移动到会话上,即可显示会话操作区,通过 action snippet 自定义渲染操作区。defaultActions 是默认按钮的 snippet 数组,用 {#each} 加 {@render} 渲染。

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';
    import { IconForward, IconMoreStroked } from '@fibrae/semi-icons-svelte';
    import { Button, Dropdown, type DropDownMenuItem } from '@fibrae/semi-ui-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };
    const menu: DropDownMenuItem[] = [{ node: 'item', name: '分享' }];

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

    let messages = $state<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(() => {
            messages = [...messages, newAssistantMessage];
        }, 200);
    };
</script>

<Chat bind:chats={messages} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px" {uploadProps}>
    {#snippet action({ defaultActions, class: actionClass })}
        <span class={actionClass}>
            {#each defaultActions as defaultAction, index (index)}
                {@render defaultAction()}
            {/each}
            <Dropdown {menu} position="top" trigger="click">
                {#snippet menuIcon()}
                    <IconForward />
                {/snippet}
                {#snippet child({ props })}
                    <Button {...props} aria-label="更多" class="semi-chat-chatBox-action-btn" theme="borderless" type="tertiary">
                        {#snippet icon()}
                            <IconMoreStroked />
                        {/snippet}
                    </Button>
                {/snippet}
            </Dropdown>
        </span>
    {/snippet}
</Chat>

通过 content snippet 自定义内容区域,需要内置内容时用 {@render defaultContent()}。

Assistant
代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';
    import { IconChevronUp } from '@fibrae/semi-icons-svelte';
    import { Avatar, AvatarGroup, MarkdownRender } from '@fibrae/semi-ui-svelte';

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

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

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

    let messages = $state<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
    let collapsed = $state(true);

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

    const buttonReset = 'border: none; background: var(--semi-color-tertiary-light-hover); border-radius: 16px; font-family: inherit; line-height: inherit';
</script>

<Chat bind:chats={messages} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 500px" {uploadProps}>
    {#snippet content({ message, class: contentClass })}
        {@const sources = sourcesOf(message)}
        <div class={contentClass}>
            {#if sources}
                <div 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); border-radius: 16px; box-sizing: border-box; margin-bottom: 10px">
                    {#if collapsed}
                        <button style="{buttonReset}; display: flex; width: fit-content; column-gap: 10px; padding: 5px 10px; cursor: pointer; font-size: 14px; color: var(--semi-color-text-1)" type="button" onclick={() => (collapsed = false)}>
                            <span>基于{sources.length}个搜索来源</span>
                            <AvatarGroup items={sources.map((source) => ({ alt: '', src: source.avatar }))} size="extra-extra-small" />
                        </button>
                    {:else}
                        <button style="{buttonReset}; width: 100%; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; padding: 12px; text-align: start; font-size: inherit; color: inherit" type="button" onclick={() => (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">
                                {#each sources as source (source.url)}
                                    <span 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={source.avatar} style="width: 16px; height: 16px; flex-shrink: 0" />
                                            <span style="color: var(--semi-color-text-2); text-overflow: ellipsis">
                                                {source.title}
                                            </span>
                                        </span>
                                        <span style="color: var(--semi-color-primary); font-size: 12px">{source.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)">
                                            {source.content}
                                        </span>
                                    </span>
                                {/each}
                            </span>
                        </button>
                    {/if}
                </div>
            {/if}
            <MarkdownRender raw={typeof message.content === 'string' ? message.content : ''} />
        </div>
    {/snippet}
</Chat>

使用 fullChatBox snippet 自定义渲染整个会话框,defaultNodes 里的各部分用 {@render defaultNodes.title()} 等渲染。

System

Hello, I'm your AI assistant.

介绍一下 semi design

Assistant

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

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';
    import { Avatar } from '@fibrae/semi-ui-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };
    const titleStyle = 'display: flex; align-items: center; justify-content: center; column-gap: 10px; padding: 5px 0px; width: fit-content';

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

    let messages = $state<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(() => {
            messages = [...messages, newAssistantMessage];
        }, 200);
    };
</script>

<Chat bind:chats={messages} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px" {uploadProps}>
    {#snippet fullChatBox({ role, message, defaultNodes, class: boxClass })}
        <div class={boxClass}>
            <div style="display: flex; flex-direction: column; row-gap: 4px;{message.role === 'user' ? ' align-items: end' : ''}">
                {#if message.role !== 'user'}
                    <span style={titleStyle}>
                        <Avatar alt="" shape="square" size="extra-small" src={role.avatar} />
                        {@render defaultNodes.title()}
                    </span>
                {/if}
                <div style="width: fit-content">
                    {@render defaultNodes.content()}
                </div>
                {@render defaultNodes.action()}
            </div>
        </div>
    {/snippet}
</Chat>

自定义渲染输入框

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

import type { Snippet } from 'svelte';

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

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

<Chat bind:chats>
    {#snippet 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)" role="presentation" onclick={onClick}>
            {@render uploadNode?.()}
            {@render inputNode()}
            {@render sendNode()}
        </div>
    {/snippet}
</Chat>

其他使用示例如下

System

Hello, I'm your AI assistant.

输入信息
代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';
    import { IconUpload } from '@fibrae/semi-icons-svelte';
    import { Button, Form, FormInput, FormUpload, type FileItem, type FormApi } from '@fibrae/semi-ui-svelte';

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

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<ChatMessage[]>([
        {
            role: 'system',
            id: '1',
            createAt: 1715676751919,
            content: "Hello, I'm your AI assistant.",
        },
    ]);

    type Send = (content?: string, attachment?: FileItem[]) => void;

    let formApi: FormApi<InputValues> | undefined;

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

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

<Chat bind:chats={messages} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 500px" {uploadProps}>
    {#snippet inputArea({ onSend })}
        <div style="display: flex; flex-direction: column; border: 1px solid var(--semi-color-border); margin: 8px 16px; border-radius: 8px; padding: 8px">
            <Form getFormApi={(api) => (formApi = api)}>
                <strong>输入信息</strong>
                <FormInput field="name" label="名称(Input)" style="width: 250px" trigger="blur" />
                <FormUpload action={uploadProps.action} field="file" label="文档">
                    <Button theme="light">
                        {#snippet icon()}
                            <IconUpload />
                        {/snippet}
                        点击上传
                    </Button>
                </FormUpload>
            </Form>
            <Button style="width: fit-content" onclick={() => submit(onSend)}>提交</Button>
        </div>
    {/snippet}
</Chat>

提示信息

通过 hints 可设置提示区域内容,点击提示内容后,提示内容将作为新的用户消息发送,并触发 onHintClick 回调。

Assistant

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

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';

    const roleConfig = {
        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 uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<ChatMessage[]>([
        {
            role: 'assistant',
            id: '1',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ]);
    let hints = $state(['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?']);

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

<Chat bind:chats={messages} {hints} onClear={() => (hints = [])} onHintClick={() => (hints = [])} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px" {uploadProps} />

自定义提示信息渲染

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

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

使用示例如下:

Assistant

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

代码
<script lang="ts">
    import { Chat, type ChatMessage } from '@fibrae/semi-ai-svelte';
    import { IconArrowRight } from '@fibrae/semi-icons-svelte';

    const roleConfig = {
        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 hintStyle = 'border: 1px solid var(--semi-color-border); padding: 10px; border-radius: 10px; background: none; color: var(--semi-color-text-1); display: flex; justify-content: space-between; align-items: center; text-align: left; cursor: pointer; font-family: inherit; font-size: 14px; line-height: inherit';
    const uploadProps = { action: 'https://api.semi.design/upload' };

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

    let messages = $state<ChatMessage[]>([
        {
            role: 'assistant',
            id: '1',
            createAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ]);
    let hints = $state(['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?']);

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

<Chat bind:chats={messages} {hints} onClear={() => (hints = [])} onHintClick={() => (hints = [])} {onMessageSend} {roleConfig} style="border: 1px solid var(--semi-color-border); border-radius: 16px; height: 400px" {uploadProps}>
    {#snippet hintBox({ content, onHintClick })}
        <button style={hintStyle} type="button" onclick={onHintClick}>
            {content}
            <IconArrowRight style="margin-inline-start: 10px" />
        </button>
    {/snippet}
</Chat>

API 参考

Props

属性说明类型默认值
action自定义消息操作栏的 snippet,参数为 message、默认按钮数组 defaultActions(snippet 数组)、按名称索引的 defaultActionsObj 和类名 class。TypeSnippet<[ChatActionSnippetProps]>-
align对话布局方式,可选 leftRight(左右分布)或 leftAlign(左对齐)。ChatAlign"leftRight"
avatar自定义消息头像的 snippet,参数为 message、role 和默认头像 defaultAvatar(snippet)。提供但渲染为空时不显示头像。TypeSnippet<[ChatAvatarSnippetProps]>-
bottomSlot输入框下方内容的 snippet。Snippet-
canSend发送按钮是否可用。通常由内部逻辑决定,设置后以此值为准。boolean-
chats对话列表,可绑定:bind:chats。发送消息、点赞、重置、清除上下文、删除时,Chat 先把新列表写回,再触发 onChatsChange。列表的整体替换、push 和最后一条消息文本的就地增长都会被跟随,其他就地修改(如深层 content 改动、单独翻转 status)会渲染但不被跟随,建议整体替换数组。ChatMessage[]-
content自定义消息内容区域的 snippet,参数为 message、role、默认内容 defaultContent(snippet)和类名 class。TypeSnippet<[ChatContentSnippetProps]>-
customMarkDownComponents自定义 Markdown 渲染组件,键为元素名,传给消息内容的 MarkdownRender。MarkdownRenderComponents-
divider自定义清除上下文后的分割线的 snippet,参数为 message。TypeSnippet<[ChatDividerSnippetProps]>-
enableUpload是否启用上传。布尔值同时控制粘贴、点击和拖拽上传;传对象时分别设置 pasteUpload、clickUpload、dragUpload,未设置的项为 true。boolean | ChatEnableUploadPropstrue
escapeHtml是否转义用户消息中的 HTML 标签,防止被 Markdown 解析器当作 HTML 处理导致内容丢失。booleantrue
fullChatBox完全自定义整个消息框的 snippet,参数为 message、role、各部分默认节点 defaultNodes(snippet)和类名 class。TypeSnippet<[ChatFullChatBoxSnippetProps]>-
hintBox自定义提示项的 snippet,参数为 content、index 和点击处理函数 onHintClick。TypeSnippet<[ChatHintBoxSnippetProps]>-
hintCls提示区最外层的类名。string-
hints提示信息列表。点击某项后触发 onHintClick,并将该项作为新的用户消息。string[]-
hintStyle提示区最外层的样式,字符串。string-
inputArea自定义输入区域的 snippet,参数为 defaultNode、各部分节点(clearContextNode、uploadNode、inputNode、sendNode,均为 snippet)以及需要自行调用的 onSend、onClear、onClick。TypeSnippet<[ChatInputAreaSnippetProps]>-
inputBoxCls输入框的类名。string-
inputBoxStyle输入框的样式,字符串。string-
markdownRenderProps传给消息内容所用 MarkdownRender 的属性,默认 format 为 md。raw 与 components 不可设置,自定义渲染组件使用 customMarkDownComponents。ChatMarkdownRenderProps-
mode对话模式,可选 bubble、noBubble 或 userBubble。ChatMode"bubble"
onChatsChange对话列表变化时的回调,在 chats 写回之后触发。Type(chats: ChatMessage[]) => void-
onClear清除上下文时的回调。Type() => void-
onHintClick点击提示信息时的回调,参数为该提示内容。Type(hint: string) => void-
onInputChange输入内容或附件变化时的回调,参数为 { inputValue, attachment }。Type(props: ChatInputChangeProps) => void-
onMessageBadFeedback消息负向反馈时的回调。Type(message: ChatMessage) => void-
onMessageCopy复制消息时的回调。Type(message: ChatMessage) => void-
onMessageDelete删除消息时的回调。Type(message: ChatMessage) => void-
onMessageGoodFeedback消息正向反馈时的回调。Type(message: ChatMessage) => void-
onMessageReset重置消息时的回调;由 resetMessage 方法触发时参数为 null。Type(message: ChatMessage | null) => void-
onMessageSend发送消息时的回调,参数为文本内容和附件。Type(content: string, attachment?: FileItem[]) => void-
onStopGenerator点击停止生成按钮时的回调。Type(event: MouseEvent) => void-
placeholder输入框占位符。string-
roleAvatar没有图片 URL 的角色的头像内容 snippet,参数为 message 和 role。TypeSnippet<[ChatRoleAvatarSnippetProps]>-
roleConfig角色信息,键为消息的 role。avatar 只接受图片 URL,节点头像使用 roleAvatar snippet;color 传给 Avatar。ChatRoleConfig-
sendHotKey发送快捷键,可选 enter 或 shift+enter。enter 时单独按 Enter 发送,Shift+Enter 换行;shift+enter 相反。SendHotKey"enter"
showClearContext是否在输入框中显示清除上下文按钮。booleanfalse
showStopGenerate是否在最后一条消息生成中时显示停止生成按钮。生成中发送按钮不可用,点击停止按钮触发 onStopGenerator。booleanfalse
title自定义消息标题的 snippet,参数为 message、role 和默认标题 defaultTitle(snippet)。提供但渲染为空时不显示标题。TypeSnippet<[ChatTitleSnippetProps]>-
topSlot对话区域顶部内容的 snippet。Snippet-
uploadButton替换上传按钮的 snippet。Snippet-
uploadProps传给 Upload 的属性,fileList 与 children 由 Chat 管理,上传按钮的内容使用 uploadButton snippet;添加或移除附件时 Chat 自己处理 onChange,其余回调照常传入。未设置 action 时为空字符串,需要自行提供 customRequest 或 action 才会上传。ChatUploadProps-
uploadTip上传按钮提示文字的 snippet。Snippet-
uploadTipProps上传按钮提示 Tooltip 的属性,不含 content;提示文字使用 uploadTip snippet。TypeOmit<TooltipProps, 'content' | 'children' | 'child'>-

Methods

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

class、style 与其他未声明的属性及事件(如 data-*、aria-*、ondragover)落在根元素 div 上,class 与组件自身的类名合并。根元素带有 aria-label 或 aria-labelledby 且没有传 role 时,自动加上 role="group"。ondragover、ondragstart、ondragend 监听同样落在根元素上,Chat 自己的拖拽上传处理先于使用方的监听执行。

RoleConfig

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

Metadata

属性说明类型默认值
name名称string-
avatar头像图片 URL。节点头像使用 roleAvatar snippetstring-
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