Plus · Chat

Chat

Used to quickly build conversation content

When to use

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

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

Demos

How to import

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

Basic usage

Bind the conversation list with bind:chats and listen to onMessageSend to achieve basic conversation display and interaction. When a message is sent, liked, reset, cleared or deleted, Chat writes the new list back to chats first and then calls onChatsChange. Replacing the list, a push and in-place growth of the last message's text are followed; other in-place edits (a deep content edit, a lone status flip) render but are not followed, so replace the array as a whole when updating a message.

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

The prompt text of the upload button is set through the uploadTip snippet, and the other properties of the Tooltip through uploadTipProps (without content). For details, please refer to Tooltip. The upload button itself can be replaced with the uploadButton snippet. uploadProps leaves out fileList and children, as Chat owns the attachment list; nothing is uploaded unless a customRequest or an action is given.

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

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

模式
会话布局方式
System

Hello, I'm your AI assistant.

User

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

Assistant

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

svelte
<Button>Click me</Button>
Code
<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}

Chat status

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

Assistant

请求成功

请求错误

Code
<script 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} />

Dynamic update chats

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

The showStopGenerate parameter can be used to determine whether to display the stop generation button, with a default value of false. The logic for stopping the generation can be handled in the onStopGenerator function. When streaming a message, replace the last item with a new object and replace the array as a whole each time, and Chat follows the update.

System

Hello, I'm your AI assistant.

User

介绍一下 Semi design

Assistant

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

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

Learn more:

  1. Introduction 介绍 - Semi Design
  2. Getting Started 快速开始 - Semi Design
  3. Semi D2C 设计稿转代码的演进之路 - 知乎
Code
<script 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} />

Clear context

Displaying the clear context button in the input box can be enabled through showClearContext, which defaults to false. The context can also be cleared by calling the clearContext method on the component instance (bind:this), which also offers resetMessage, scrollToBottom and sendMessage; see Methods in the API. The divider after clearing can be customized with the 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()}>Clear context</button>
System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

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

Custom rendering dialog box

Customize the parts of the chat box through the snippets of the same names; the snippet parameter types are as follows. The class in the parameters is the default class name of that part, and a custom root element needs to bind it; default* are the built-in nodes, of type snippet, rendered with {@render} where needed.

import type { Snippet } from 'svelte';

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

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

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

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

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

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

/* Each part is the message's default rendering, including what the avatar, title, content and action snippets give */
export interface ChatFullChatBoxNodes {
    avatar: Snippet;
    title: Snippet;
    content: Snippet;
    action: Snippet;
}

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

Customize the avatar and the title through the avatar and title snippets. When a snippet is provided but renders nothing, the avatar or the title is hidden; the avatar of roleConfig accepts an image URL only, so use the roleAvatar snippet for an avatar node.

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

Hello, I'm your AI assistant.

这张图片里有什么?

图片

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

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

When hovering over a conversation, the conversation action area will be displayed. You can customize the rendering of the action area using the action snippet. defaultActions is an array of snippets of the default buttons, rendered with {#each} and {@render}.

System

Hello, I'm your AI assistant.

User

介绍一下 semi design

Assistant

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

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

You can customize the content area using the content snippet; render the built-in content with {@render defaultContent()} when needed.

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

Use the fullChatBox snippet to custom render the entire chat box; render the parts in defaultNodes with {@render defaultNodes.title()} and the like.

System

Hello, I'm your AI assistant.

介绍一下 semi design

Assistant

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

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

Custom render InputArea

The input area can be customized through the inputArea snippet, the parameters are as follows

import type { Snippet } from 'svelte';

export interface ChatInputAreaSnippetProps {
    /* Default node */
    defaultNode: Snippet;
    /* Clear context button */
    clearContextNode: Snippet;
    /* Upload button, null when every upload way is off */
    uploadNode: Snippet | null;
    /* Text input */
    inputNode: Snippet;
    /* Send button */
    sendNode: Snippet;
    /* If the input is customized, call it when sending a message */
    onSend: (content?: string, attachment?: FileItem[]) => void;
    /* If the clear context button is customized, call it when clearing the context */
    onClear: () => void;
    /* Handler that focuses the text input on click */
    onClick: () => void;
}

An example of using the part nodes is as follows

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

Other Example:

System

Hello, I'm your AI assistant.

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

Hint

The prompt area content can be set through hints. After clicking the prompt content, it is sent as a new user message and triggers the onHintClick callback.

Assistant

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

Code
<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} />

Custom render Hint

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

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

Example:

Assistant

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

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

Props

PropertyDescriptionTypeDefault
actionSnippet that customizes the message action bar, with message, the default buttons defaultActions (an array of snippets), defaultActionsObj indexed by name, and the class name class.TypeSnippet<[ChatActionSnippetProps]>-
alignConversation layout: leftRight or leftAlign.ChatAlign"leftRight"
avatarSnippet that customizes the message avatar, with message, role and the built-in avatar defaultAvatar (a snippet). Provided and rendering nothing, it hides the avatar.TypeSnippet<[ChatAvatarSnippetProps]>-
bottomSlotSnippet for the content below the input box.Snippet-
canSendWhether the send button is enabled. It is decided internally by default; when set, this value wins.boolean-
chatsThe conversation list, bindable with bind:chats. When a message is sent, liked, reset, cleared or deleted, Chat writes the new list back first and then calls onChatsChange. Replacing the list, a push and in-place growth of the last message's text are followed; other in-place edits (a deep content edit, a lone status flip) render but are not followed, so replace the array as a whole.ChatMessage[]-
contentSnippet that customizes the message content area, with message, role, the built-in content defaultContent (a snippet) and the class name class.TypeSnippet<[ChatContentSnippetProps]>-
customMarkDownComponentsCustom Markdown render components, keyed by element name, passed to the MarkdownRender of the message content.MarkdownRenderComponents-
dividerSnippet that customizes the divider after the context is cleared, with message.TypeSnippet<[ChatDividerSnippetProps]>-
enableUploadWhether upload is enabled. A boolean controls paste, click and drag upload together; an object sets pasteUpload, clickUpload and dragUpload separately, and an unset item is true.boolean | ChatEnableUploadPropstrue
escapeHtmlWhether to escape HTML tags in the user's messages, so the Markdown parser does not treat them as HTML and drop the content.booleantrue
fullChatBoxSnippet that customizes the whole message box, with message, role, the default nodes defaultNodes (snippets) and the class name class.TypeSnippet<[ChatFullChatBoxSnippetProps]>-
hintBoxSnippet that customizes a hint, with content, index and the click handler onHintClick.TypeSnippet<[ChatHintBoxSnippetProps]>-
hintClsClass name of the outermost element of the hints area.string-
hintsThe list of hints. Clicking one calls onHintClick and sends it as a new user message.string[]-
hintStyleStyle of the outermost element of the hints area, a string.string-
inputAreaSnippet that customizes the input area, with defaultNode, the part nodes (clearContextNode, uploadNode, inputNode, sendNode, all snippets), and onSend, onClear and onClick, which you call yourself.TypeSnippet<[ChatInputAreaSnippetProps]>-
inputBoxClsClass name of the input box.string-
inputBoxStyleStyle of the input box, a string.string-
markdownRenderPropsProps passed to the MarkdownRender that renders the message content; the default format is md. raw and components cannot be set; use customMarkDownComponents for custom render components.ChatMarkdownRenderProps-
modeConversation mode: bubble, noBubble or userBubble.ChatMode"bubble"
onChatsChangeCalled when the conversation list changes, after chats is written back.Type(chats: ChatMessage[]) => void-
onClearCalled when the context is cleared.Type() => void-
onHintClickCalled when a hint is clicked, with the hint text.Type(hint: string) => void-
onInputChangeCalled when the input or the attachments change, with { inputValue, attachment }.Type(props: ChatInputChangeProps) => void-
onMessageBadFeedbackCalled when a message gets negative feedback.Type(message: ChatMessage) => void-
onMessageCopyCalled when a message is copied.Type(message: ChatMessage) => void-
onMessageDeleteCalled when a message is deleted.Type(message: ChatMessage) => void-
onMessageGoodFeedbackCalled when a message gets positive feedback.Type(message: ChatMessage) => void-
onMessageResetCalled when a message is reset; the argument is null when it comes from the resetMessage method.Type(message: ChatMessage | null) => void-
onMessageSendCalled when a message is sent, with the text and the attachments.Type(content: string, attachment?: FileItem[]) => void-
onStopGeneratorCalled when the stop button is clicked.Type(event: MouseEvent) => void-
placeholderPlaceholder of the input box.string-
roleAvatarSnippet for the avatar content of a role without an image URL, with message and role.TypeSnippet<[ChatRoleAvatarSnippetProps]>-
roleConfigRole information, keyed by the message role. avatar takes an image URL only; an avatar node goes through the roleAvatar snippet. color is passed to Avatar.ChatRoleConfig-
sendHotKeySend hot key: enter or shift+enter. With enter, Enter sends and Shift+Enter inserts a line break; shift+enter is the reverse.SendHotKey"enter"
showClearContextWhether to show the clear context button in the input box.booleanfalse
showStopGenerateWhether to show the stop button while the last message is generating. Sending is disabled meanwhile, and clicking the stop button calls onStopGenerator.booleanfalse
titleSnippet that customizes the message title, with message, role and the built-in title defaultTitle (a snippet). Provided and rendering nothing, it hides the title.TypeSnippet<[ChatTitleSnippetProps]>-
topSlotSnippet for the content at the top of the conversation area.Snippet-
uploadButtonSnippet that replaces the upload button.Snippet-
uploadPropsProps passed to Upload. Chat owns fileList and children, and the upload button content is the uploadButton snippet; Chat handles onChange itself when an attachment is added or removed, and the other callbacks pass through. action falls back to an empty string, so nothing is uploaded unless a customRequest or an action is given.ChatUploadProps-
uploadTipSnippet for the upload button's tip text.Snippet-
uploadTipPropsProps of the upload button's Tooltip, without content; the tip text is the uploadTip snippet.TypeOmit<TooltipProps, 'content' | 'children' | 'child'>-

Methods

MethodDescriptionType
clearContextAppends a divider at the end of the conversation to clear the context and calls onClear; it has no effect when the list already ends with a divider.Type() => void
resetMessageResets the last message to an empty loading state and calls onMessageReset with null.Type() => void
scrollToBottomScrolls to the bottom, smoothly when animation is true, otherwise it jumps.Type(animation?: boolean) => void
sendMessageSends a message; without attachment the message content is a string.Type(text: string, attachment?: FileItem[]) => void

class, style and the other undeclared attributes and events (such as data-*, aria-* and ondragover) land on the root div, and class is merged with the component's own class name. When the root has aria-label or aria-labelledby and no role, it gets role="group". The ondragover, ondragstart and ondragend listeners also land on the root, and Chat's own drag-upload handling runs before the caller's listener.

RoleConfig

PROPERTIESINSTRUCTIONSTYPEDEFAULT
userUser informationChatMetadata-
assistantAssistant informationChatMetadata-
systemSystem informationChatMetadata-

Metadata

PROPERTIESINSTRUCTIONSTYPEDEFAULT
namenamestring-
avataravatar image URL; an avatar node goes through the roleAvatar snippetstring-
colorAvatar background color, same as the color parameter of Avatar component, support amber、 blue、 cyan、 green、 grey、 indigo、 light-blue、 light-green、 lime、 orange、 pink、 purple、 red、 teal、 violet、 yellowAvatarColorgrey

Message

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

Content

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

Accessibility

Keyboard and Focus

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

Design Token

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