使用场景
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)。
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
svelte
代码
<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 不同,会话样式不同。
请求成功
请求错误
代码
<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 会跟随更新。
Hello, I'm your AI assistant.
介绍一下 Semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[1]。 Semi Design的特点包括:
- 设计简洁、现代化。
- 提供主题方案,可深度样式定制。
- 提供明暗色两套模式,切换方便。
- 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
- 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
Learn more:
代码
<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>
Hello, I'm your AI assistant.
介绍一下 semi design
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} 渲染。
Hello, I'm your AI assistant.
介绍一下 semi design
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()}。
代码
<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()} 等渲染。
Hello, I'm your AI assistant.
介绍一下 semi design
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>
其他使用示例如下
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 回调。
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;
}
使用示例如下:
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 | ChatEnableUploadProps | true |
| escapeHtml | 是否转义用户消息中的 HTML 标签,防止被 Markdown 解析器当作 HTML 处理导致内容丢失。 | boolean | true |
| 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 | 是否在输入框中显示清除上下文按钮。 | boolean | false |
| showStopGenerate | 是否在最后一条消息生成中时显示停止生成按钮。生成中发送按钮不可用,点击停止按钮触发 onStopGenerator。 | boolean | false |
| 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 snippet | string | - |
| color | 头像背景色,同 Avatar 组件的 color 参数, 支持 amber、 blue、 cyan、 green、 grey、 indigo、 light-blue、 light-green、 lime、 orange、 pink、 purple、 red、 teal、 violet、 yellow | AvatarColor | grey |
Message
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| role | 角色 | string | - |
| name | 名称 | string | - |
| id | 唯一标识 | string | - |
| content | 文本内容 | string | Content[] | - |
| parentId | 父节点id | string | - |
| createAt | 创建时间 | number | - |
| status | 消息状态,可选值为 loading | incomplete | complete | error | string | complete |
Content
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 类型, 可选值text | image_url | file_url | string | - |
| 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 变量 | 值 | 用法 |
|---|---|---|
| $radius-chat_chatBox_content | var(--semi-border-radius-large) | 聊天框内容圆角 |
| $radius-chat_inputBox_container | 16px | 输入框容器圆角 |
| $radius-chat_attachment_img | var(--semi-border-radius-medium) | 附件图片圆角 |
| $radius-chat_attachment_file | var(--semi-border-radius-medium) | 附件文件圆角 |
| $radius-chat_hint_item | var(--semi-border-radius-large) | 提示条圆角 |
| $radius-chat_chatBox_content_code | var(--semi-border-radius-large) | 代码块圆角 |
| $radius-chat_chatBox_content_code_topSlot_copy | var(--semi-border-radius-large) | 代码块顶部复制按钮圆角 |
| $radius-chat_dropArea | 16px | 拖拽上传区域圆角 |
| $color-chat_action_content-bg | var(--semi-color-bg-0) | 返回按钮/停止生成内容按钮背景颜色 |
| $color-chat_action_content-border | var(--semi-color-border) | 返回按钮/停止生成按钮描边颜色 |
| $color-chat_divider | var(--semi-color-text-2) | 分割线文字颜色 |
| $color-chat_chatBox_title | var(--semi-color-text-0) | 聊天框标题颜色 |
| $color-chat_chatBox_action_icon | var(--semi-color-text-2) | 聊天框操作区域按钮图标颜色 |
| $color-chat_chatBox_action_icon-hover | var(--semi-color-text-0) | 聊天框操作区域按钮图标hover颜色 |
| $color-chat_chatBox_action-bg-hover | transparent | 聊天框操作区域按钮hover背景颜色 |
| $color-chat_chatBox_content_text | var(--semi-color-text-0) | 聊天框内容文字颜色 |
| $color-chat_chatBox_content_bg | var(--semi-color-fill-0) | 聊天框内容背景颜色 |
| $color-chat_chatBox_content_user-bg | var(--semi-color-primary) | 聊天框内容用户背景颜色 |
| $color-chat_chatBox_content_user-text | var(--semi-color-white) | 聊天框内容用户文字颜色 |
| $color-chat_chatBox_content_error-bg | var(--semi-color-danger-hover) | 聊天框内容错误背景颜色 |
| $color-chat_chatBox_content_error-text | var(--semi-color-white) | 聊天框内容错误文字颜色 |
| $color-chat_inputBottom_clearButton_icon | var(--semi-color-text-2) | 清空按钮图标颜色 |
| $color-chat_inputBottom_uploadButton_icon | var(--semi-color-text-0) | 上传按钮图标颜色 |
| $color-chat_inputBottom_sendButton_icon-disable | var(--semi-color-primary-disabled) | 发送按钮禁用态图标颜色 |
| $color-chat_inputBox_container-border | var(--semi-color-border) | 输入框容器边框颜色 |
| $color-chat_attachment_clear_icon | var(--semi-color-text-2) | 附件清除图标颜色 |
| $color-chat_attachment_file-bg | var(--semi-color-fill-0) | 附件文件背景颜色 |
| $color-chat_attachment_fail | var(--semi-color-danger) | 附件上传失败的提示图标颜色 |
| $color-chat_chatBox_user_attachment_file-bg | var(--semi-color-bg-0) | 用户聊天框附件文件背景颜色 |
| $color-chat_chatBox_other_attachment_file-bg | var(--semi-color-fill-2) | 聊天框附件文件背景颜色 |
| $color-chat_attachment_file_icon | var(--semi-color-text-2) | 附件文件图标颜色 |
| $color-chat_attachment_file_title | var(--semi-color-text-0) | 附件文件标题颜色 |
| $color-chat_attachment_file_metadata_text | var(--semi-color-text-2) | 附件文件元数据文字颜色 |
| $color-chat_hint_item-border | var(--semi-color-border) | 提示条边框颜色 |
| $color-chat_hint_item-bg | transparent | 提示条背景颜色 |
| $color-chat_hint_item-bg-hover | var(--semi-color-fill-0) | 提示条hover背景颜色 |
| $color-chat_hint_content_text | var(--semi-color-text-1) | 提示条文字颜色 |
| $color-chat_hint_icon | var(--semi-color-text-2) | 提示条图标颜色 |
| $color-chat_chatBox_loading-bg | var(--semi-color-text-0) | 聊天内容加载图标圆圈颜色 |
| $color-chat_chatBox_code_topSlot | var(--semi-white) | 代码块顶部字体颜色 |
| $color-chat_chatBox_code_topSlot-bg | var(--semi-grey-4) | 代码块顶部背景色 |
| $color-chat_chatBox_code_topSlot_toCopy-bg-hover | var(--semi-grey-5) | 代码块顶部复制按钮hover背景色 |
| $color-chat_chatBox_code_content | var(--semi-color-bg-0) | 代码块内容背景色 |
| $color-chat_action_content-bg-hover | var(--semi-color-tertiary-light-hover) | 返回按钮/停止生成按钮hover背景颜色 |
| $color-chat_dropArea-bg | rgb(from var(--semi-grey-2) r g b / 0.9) | 拖拽区域文字颜色 |
| $color-chat_dropArea-border | var(--semi-color-border) | 拖拽区域边框颜色 |
| $spacing-chat_paddingY | 12px | chat组件上下内边距 |
| $spacing-chat_container-paddingX | 16px | 消息框水平内边距 |
| $spacing-chat_action_content-bottom | 0 | 返回按钮/停止生成按钮底部边距 |
| $spacing-chat_chatBox-marginY | 8px | 聊天框上下外边距 |
| $spacing-chat_chatBox-columnGap | 12px | 聊天框内容列间距 |
| $spacing-chat_chatBox_action-columnGap | 10px | 聊天框操作区域按钮列间距 |
| $spacing-chat_chatBox_action-marginX | 10px | 聊天框操作区域左右外边距 |
| $spacing-chat_chatBox_action_btn-padding | 0 | 聊天框操作区域按钮内边距 |
| $spacing-chat_chatBox_content-paddingY | 8px | 聊天框内容上下内边距 |
| $spacing-chat_chatBox_content-paddingX | 12px | 聊天框内容左右内边距 |
| $spacing-chat_inputBox-paddingTop | 8px | 输入框顶部内边距 |
| $spacing-chat_inputBox-paddingBottom | 8px | 输入框底部内边距 |
| $spacing-chat_inputBox-paddingX | 16px | 输入框左右内边距 |
| $spacing-chat_inputBox_container-padding | 11px | 输入框容器内边距 |
| $spacing-chat_inputBox_inner-columnGap | 4px | 输入框容器列间距 |
| $spacing-chat_inputBox-marginY | 4px | |
| $spacing-chat_attachment-columnGap | 10px | 附件列间距 |
| $spacing-chat_attachment-RowGap | 5px | 附件行间距 |
| $spacing-chat_attachment-marginX | 12px | 附件左右外边距 |
| $spacing-chat_attachment_clear-top | 8px | 附件清除图标顶部间距 |
| $spacing-chat_attachment_clear-right | 8px | 附件清除图标右内边距 |
| $spacing-chat_attachment_file-columnGap | 5px | 文件附件列间距 |
| $spacing-chat_attachment_file-padding | 5px | 文件附件内边距 |
| $spacing-chat_chatBox_loading_item-gap | 15px | 聊天内容加载图标间距 |
| $spacing-chat_divider-marginY | 12px | 分割线上下外边距 |
| $spacing-chat_chatBox_content_attachment-marginY | 4px | 聊天框内容文件/图片上下外间距 |
| $spacing-chat_chatBox_content_attachment-marginRight | 4px | 聊天框内容文件/图片上下右间距 |
| $spacing-chat_chatBox_content_code_topSlot-paddingX | 5px | 聊天框代码块顶部上下内边距 |
| $spacing-chat_chatBox_content_code_topSlot-paddingY | 8px | 聊天框代码块顶部左右内边距 |
| $spacing-chat_chatBox_content_code_topSlot_copy-columnGap | 5px | 聊天框代码块顶部复制按钮列间距 |
| $spacing-chat_chatBox_content_code_topSlot_copy-padding | 5px | 聊天框代码块顶部复制按钮列间距 |
| $spacing-chat_chatBox_wrap | 8px | 聊天框外层间距 |
| $spacing-chat_hint-rowGap | 10px | 提示条行间距 |
| $spacing-chat_hint-marginY | 12px | 提示条容器上下外边距 |
| $spacing-chat_hint-marginLeft | 34px | 提示条容器左外边距 |
| $spacing-chat_hint_item-marginY | 8px | 提示条上下外边距 |
| $spacing-chat_hint_item-marginX | 12px | 提示条左右外边距 |
| $spacing-chat_hint_item-columnGap | 20px | 提示条内容列间距 |
| $spacing-chat_chatBox_loading-item-marginX | 18px | 聊天内容加载图标中心圆圈左右外边距 |
| $spacing-chat_chatBox_loading-item-marginY | 6px | 聊天内容加载图标中心圆圈上下外边距 |
| $width-chat_backBottom_wrapper | 42px | 返回按钮宽度 |
| $width-chat_action_content-border | 1px | 返回按钮/停止生成按钮描边宽度 |
| $width-chat_inputBottom_clearButton | 48px | 清空按钮宽度 |
| $width-chat_inputBottom_uploadButton | 32px | 上传按钮宽度 |
| $width-chat_inputBottom_sendButton | 32px | 发送按钮宽度 |
| $width-chat_inputBox_container-border | 1px | 输入框容器边框宽度 |
| $width-chat_hint_item-border | 1px | 提示条边框宽度 |
| $width-chat_chatBox_loading | 8px | 加载中单个圆圈图标宽度 |
| $width-chat_attachment_file_title | 90px | 附件文件标题最大宽度 |
| $width-chat_max | 800px | chat组件最大宽度 |
| $width-chat_dropArea-border | 5px | 拖拽上传边框宽度 |
| $width-chat_chatBox_content_code_topSlot_copy | 150px | 聊天框代码块顶部复制按钮最小宽度 |
| $width-chat_chatBox_avatar | 24px | 聊天框头像宽度 |
| $height-chat_action_stop | 42px | 停止生成按钮高度 |
| $font-chat_divider-fontWeight | $font-weight-regular | 分割线字重 |
| $font-chat_divider-fontSize | $font-size-small | 分割线字体大小 |
| $font-chat_chatBox_title-lineHeight | 20px | 聊天框标题行高 |
| $font-chat_chatBox_title-fontSize | $font-size-header-6 | 聊天框标题字体大小 |
| $font-chat_chatBox_title-fontWeight | $font-weight-regular | 聊天框标题字重 |
| $font-chat_inputBottom_clearButton_icon-fontSize | 30px | 输入区清空上下文按钮图标大小 |
| $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_topSlot | 12px | 代码块顶部字体大小 |
| $font-chat_chatBox_code_topSlot-lineHeight | 16px | 代码块顶部区域字体行高 |
| $font-chat_dropArea_text | 48px | 拖拽上传区域文字大小 |
| $z-chat_dropArea | 10 | 拖拽上传区域z-index |
| $z-chat_action | 1 | 返回按钮/停止生成按钮z-index |