When to use
The Chat component can be used in scenarios such as regular conversations or AI conversations.
The rendering of the conversation content is based on the MarkdownRender component, which supports Markdown and MDX (Note: The default format mode of MarkdownRender in Chat is md. If you need to use MDX format, you can set it through the markdownRenderProps API). It allows for common rich text features such as images, tables, links, bold text, code blocks, and more. More complex customization is achieved with scoped slots. Register the languages of code blocks with CodeHighlight.register of CodeHighlight to highlight them.
Demos
How to import
<script setup lang="ts">
import { Chat } from '@fibrous/semi-ai-vue';
</script>
Basic usage
Bind the conversation list with v-model:chats and listen to @messageSend to achieve basic conversation display and interaction. When the list changes, v-model:chats first emits update:chats, then chatsChange; with a one-way :chats that is not written back, the list stays unchanged.
Attachments can be uploaded by clicking the upload button, pasting into the input box, or dragging files into the Chat area. Set the upload parameters through uploadProps. For details, refer to Upload.
The prompt text of the upload button is set through the #uploadTip slot, and the other properties of the Tooltip through uploadTipProps (without content). For details, please refer to Tooltip.
Dialogue is a scene involving multiple parties and multiple rounds of interaction. Role information (including name, avatar, etc.) can be passed in through roleConfig, and the specific parameter details are RoleConfig.
Use the align attribute to set the layout of the dialog, supporting left and right layout (leftRight, default) and left alignment (leftAlign).
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
jsx
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage, type ChatProps } from '@fibrous/semi-ai-vue';
import { Radio, RadioGroup, type RadioValue } from '@fibrous/semi-ui-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
margin: '8px 16px',
height: '550px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const message = ref<ChatMessage[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: '给一个 Semi Design 的 Button 组件的使用示例',
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: "以下是一个 Semi 代码的使用示例:\n```jsx \nimport React from 'react';\nimport { Button } from '@fibrous/semi-ui-react';\n\nconst MyComponent = () => {\n return (\n <Button>Click me</Button>\n );\n};\nexport default MyComponent;\n```\n",
},
]);
const mode = ref<RadioValue | undefined>('bubble');
const align = ref<RadioValue | undefined>('leftRight');
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
const onMessageReset = () => {
setTimeout(() => {
const lastMessage = message.value[message.value.length - 1];
const newLastMessage: ChatMessage = {
...lastMessage,
status: 'complete',
content: 'This is a mock reset message.',
};
message.value = [...message.value.slice(0, -1), newLastMessage];
}, 200);
};
</script>
<template>
<span style="display: flex; flex-direction: column; row-gap: 8px">
<span style="display: flex; align-items: center; column-gap: 10px">
模式
<RadioGroup v-model="mode" type="button">
<Radio value="bubble">气泡</Radio>
<Radio value="noBubble">非气泡</Radio>
<Radio value="userBubble">用户会话气泡</Radio>
</RadioGroup>
</span>
<span style="display: flex; align-items: center; column-gap: 10px">
会话布局方式
<RadioGroup v-model="align" type="button">
<Radio value="leftRight">左右分布</Radio>
<Radio value="leftAlign">左对齐</Radio>
</RadioGroup>
</span>
</span>
<Chat :key="`${align}${mode}`" v-model:chats="message" :align="align as ChatProps['align']" :mode="mode as ChatProps['mode']" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageReset="onMessageReset" @messageSend="onMessageSend">
<template #uploadTip>自定义上传按钮提示信息</template>
</Chat>
</template>Chat status
The chats type is Message[], where each Message contains various information about the conversation, such as role, content, attachment, status, unique identifier (id), creation time (createAt), and more. For detailed information, please refer to Message. The conversation style may vary depending on the different status values.
请求成功
请求错误
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const messages = ref<Message[]>([
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: '请求成功',
},
{
id: 'loading',
role: 'assistant',
status: 'loading',
},
{
role: 'assistant',
id: 'error',
content: '请求错误',
status: 'error',
},
]);
const onMessageSend = () => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
messages.value = [...messages.value, newAssistantMessage];
}, 200);
};
</script>
<template>
<Chat v-model:chats="messages" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend" />
</template>Dynamic update chats
For the case of receiving Server-Sent Event data from the backend, the obtained data can be used to update the chats, and the conversation content will be updated in real-time.
The showStopGenerate parameter can be used to determine whether to display the stop generation button, with a default value of false. The logic for stopping the generation can be handled in the @stopGenerator listener.
Hello, I'm your AI assistant.
介绍一下 Semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[1]。
Semi Design的特点包括:
- 设计简洁、现代化。
- 提供主题方案,可深度样式定制。
- 提供明暗色两套模式,切换方便。
- 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
- 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
Learn more:
Code
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '600px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const messages = ref<Message[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: '介绍一下 Semi design',
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: `
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[[1]](https://semi.design/zh-CN/start/introduction)。
Semi Design的特点包括:
1. 设计简洁、现代化。
2. 提供主题方案,可深度样式定制。
3. 提供明暗色两套模式,切换方便。
4. 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
5. 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
---
Learn more:
1. [Introduction 介绍 - Semi Design](https://semi.design/zh-CN/start/introduction)
2. [Getting Started 快速开始 - Semi Design](https://semi.design/zh-CN/start/getting-started)
3. [Semi D2C 设计稿转代码的演进之路 - 知乎](https://zhuanlan.zhihu.com/p/667189184)
`,
},
]);
let timer: ReturnType<typeof setInterval> | null = null;
const stopTimer = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};
const replaceLast = (update: (last: Message) => Message) => {
messages.value = [...messages.value.slice(0, -1), update(messages.value[messages.value.length - 1])];
};
const generateMockResponse = (content: string) => {
timer = setInterval(() => {
const last = messages.value[messages.value.length - 1];
if (last.status === 'loading') {
replaceLast((m) => ({ ...m, content: `mock Response for ${content} \n`, status: 'incomplete' }));
} else if (last.status === 'incomplete') {
const next = `${last.content} mock stream message`;
if ((last.content?.length ?? 0) > 200) {
stopTimer();
replaceLast((m) => ({ ...m, content: next, status: 'complete' }));
} else {
replaceLast((m) => ({ ...m, content: next }));
}
}
}, 400);
};
const onMessageSend = (content: string) => {
messages.value = [...messages.value, { role: 'assistant', status: 'loading', createAt: Date.now(), id: getId() }];
generateMockResponse(content);
};
const onStopGenerator = () => {
if (!timer) {
return;
}
stopTimer();
const last = messages.value[messages.value.length - 1];
if (last.status && last.status !== 'complete') {
replaceLast((m) => ({ ...m, status: 'complete' }));
}
};
onBeforeUnmount(stopTimer);
</script>
<template>
<Chat v-model:chats="messages" :roleConfig="roleInfo" showStopGenerate :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend" @stopGenerator="onStopGenerator" />
</template>Clear context
Displaying the clear context button in the input box can be enabled through showClearContext, which defaults to false. The context can also be cleared by calling the clearContext method through a template ref.
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
margin: '8px 16px',
height: '550px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const message = ref<ChatMessage[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: '介绍一下 semi design',
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
},
]);
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
const onMessageReset = () => {
setTimeout(() => {
const lastMessage = message.value[message.value.length - 1];
const newLastMessage: ChatMessage = {
...lastMessage,
status: 'complete',
content: 'This is a mock reset message.',
};
message.value = [...message.value.slice(0, -1), newLastMessage];
}, 200);
};
</script>
<template>
<Chat v-model:chats="message" :roleConfig="roleInfo" showClearContext :style="commonOuterStyle" :uploadProps="uploadProps" @messageReset="onMessageReset" @messageSend="onMessageSend">
<template #uploadTip>自定义上传按钮提示信息</template>
</Chat>
</template>Custom rendering dialog box
Each part of the dialog box is customized with a scoped slot. The slot parameter types are as follows. The class in the slot parameters is the default class name of that part; the custom root element needs to bind it.
export interface ChatTitleSlotProps {
message: ChatMessage;
role: ChatMetadata;
defaultTitle: VNode;
}
export interface ChatAvatarSlotProps {
message: ChatMessage;
role: ChatMetadata;
defaultAvatar: VNode;
}
/* For a role without an image URL, renders the content inside the avatar */
export interface ChatRoleAvatarSlotProps {
message: ChatMessage;
role: ChatMetadata;
}
export interface ChatContentSlotProps {
message: ChatMessage;
role: ChatMetadata;
defaultContent: VNode;
class: string;
}
/* The buttons the default action bar shows; a key is missing when its button is not shown (e.g. an unfinished message) */
export interface ChatDefaultActionNodeObj {
copyNode?: VNode;
likeNode?: VNode;
dislikeNode?: VNode;
resetNode?: VNode;
deleteNode?: VNode;
}
export interface ChatActionSlotProps {
message: ChatMessage;
defaultActions: VNode[];
defaultActionsObj: ChatDefaultActionNodeObj;
class: string;
}
/* Each part as the message would render it, including what the avatar, title, content and action slots give */
export interface ChatFullChatBoxNodes {
avatar: VNode;
title: VNode;
content: VNode;
action: VNode;
}
export interface ChatFullChatBoxSlotProps {
message: ChatMessage;
role: ChatMetadata;
defaultNodes: ChatFullChatBoxNodes;
class: string;
}
Custom render avatar and title through the #avatar and #title slots. A provided but empty slot hides the avatar or title; the avatar of roleConfig only accepts an image URL, so use the #roleAvatar slot for a node avatar.
Hello, I'm your AI assistant.
这张图片里有什么?
图片中是一个有卡通画像装饰的黄色背包。
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
import { Avatar, Radio, RadioGroup, type RadioValue } from '@fibrous/semi-ui-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const formatTime = (createAt = Date.now()) => {
const date = new Date(createAt);
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
};
const message = ref<ChatMessage[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: [
{
type: 'text',
text: '这张图片里有什么?',
},
{
type: 'image_url',
image_url: {
url: '/assets/root-web-sites/edit-bag.jpeg',
},
},
],
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: '图片中是一个有卡通画像装饰的黄色背包。',
},
]);
const title = ref<RadioValue | undefined>('null');
const avatar = ref<RadioValue | undefined>('null');
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
content: 'This is a mock response',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
</script>
<template>
<span style="display: flex; flex-direction: column; row-gap: 8px; margin-bottom: 5px">
<span style="display: flex; align-items: center; column-gap: 10px">
头像渲染模式
<RadioGroup v-model="avatar" type="button">
<Radio value="default">默认头像</Radio>
<Radio value="null">无头像</Radio>
<Radio value="custom">自定义头像</Radio>
</RadioGroup>
</span>
<span style="display: flex; align-items: center; column-gap: 10px">
标题渲染模式
<RadioGroup v-model="title" type="button">
<Radio value="default">默认标题</Radio>
<Radio value="null">无标题</Radio>
<Radio value="custom">自定义标题</Radio>
</RadioGroup>
</span>
</span>
<Chat :key="`${avatar}${title}`" v-model:chats="message" class="component-chat-demo-custom-render" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
<template v-if="avatar === 'custom'" #avatar="{ role }">
<Avatar shape="square" size="extra-small" style="flex-shrink: 0">{{ role.name }}</Avatar>
</template>
<template v-else-if="avatar === 'null'" #avatar />
<template v-if="title === 'custom'" #title="{ role, message: item }">
<span class="title">
{{ role.name }}
<span class="time">{{ formatTime(item.createAt) }}</span>
</span>
</template>
<template v-else-if="title === 'null'" #title />
</Chat>
</template>When hovering over a conversation, the conversation action area will be displayed. You can customize the rendering of the action area using the #action slot.
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
import { IconForward, IconMoreStroked } from '@fibrous/semi-icons-vue';
import { Button, Dropdown } from '@fibrous/semi-ui-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const message = ref<ChatMessage[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: '介绍一下 semi design',
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
},
]);
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
content: 'This is a mock response',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
</script>
<template>
<Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
<template #action="{ defaultActions, class: actionClass }">
<span :class="actionClass">
<component :is="action" v-for="(action, index) in defaultActions" :key="index" />
<Dropdown position="top" trigger="click">
<Button aria-label="更多" class="semi-chat-chatBox-action-btn" theme="borderless" type="tertiary">
<template #icon>
<IconMoreStroked />
</template>
</Button>
<template #content>
<Dropdown.Menu>
<Dropdown.Item>
<template #icon>
<IconForward />
</template>
分享
</Dropdown.Item>
</Dropdown.Menu>
</template>
</Dropdown>
</span>
</template>
</Chat>
</template>You can customize the content area using the #content slot.
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
import { IconChevronUp } from '@fibrous/semi-icons-vue';
import { Avatar, AvatarGroup, MarkdownRender } from '@fibrous/semi-ui-vue';
interface Source {
avatar: string;
url: string;
title: string;
subTitle: string;
content: string;
}
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '500px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const sourcesOf = (item: ChatMessage): Source[] | undefined => item.source;
const message = ref<ChatMessage[]>([
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。它作为全面、易用、优质的现代应用 UI 解决方案,从字节跳动各业务线的复杂场景提炼而来,支撑近千计平台产品,服务内外部 10 万+ 用户。',
source: [
{
avatar: '/assets/placeholder/logo.svg',
url: '/zh-CN/start/introduction',
title: 'semi Design',
subTitle: 'Semi design website',
content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。',
},
{
avatar: '/assets/placeholder/logo.svg',
url: '/dsm/landing',
subTitle: 'Semi DSM website',
title: 'Semi 设计系统',
content: '从 Semi Design,到 Any Design 快速定义你的设计系统,并应用在设计稿和代码中',
},
{
avatar: '/assets/placeholder/logo.svg',
url: '/code/zh-CN/start/introduction',
subTitle: 'Semi D2C website',
title: '设计稿转代码',
content: 'Semi 设计稿转代码(Semi Design to Code,或简称 Semi D2C),是由抖音前端 Semi Design 团队推出的全新的提效工具',
},
],
},
]);
// Only the first message carries sources, so one open state serves the whole list
const collapsed = ref(true);
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
content: 'This is a mock response',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
const buttonReset = {
border: 'none',
background: 'var(--semi-color-tertiary-light-hover)',
borderRadius: '16px',
fontFamily: 'inherit',
lineHeight: 'inherit',
};
</script>
<template>
<Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
<template #content="{ message: item, class: contentClass }">
<div :class="contentClass">
<div
v-if="sourcesOf(item)"
:style="{
transition: collapsed ? 'height 0.4s ease, width 0.4s ease' : 'height 0.4s ease',
height: collapsed ? '30px' : '200px',
width: collapsed ? '190px' : '100%',
background: 'var(--semi-color-tertiary-light-hover)',
borderRadius: '16px',
boxSizing: 'border-box',
marginBottom: '10px',
}"
>
<button
v-show="collapsed"
:style="{
...buttonReset,
display: 'flex',
width: 'fit-content',
columnGap: '10px',
padding: '5px 10px',
cursor: 'pointer',
fontSize: '14px',
color: 'var(--semi-color-text-1)',
}"
type="button"
@click="collapsed = false"
>
<span>基于{{ sourcesOf(item)?.length }}个搜索来源</span>
<AvatarGroup size="extra-extra-small">
<Avatar v-for="(s, index) in sourcesOf(item)" :key="index" alt="" :src="s.avatar" />
</AvatarGroup>
</button>
<button
v-show="!collapsed"
:style="{
...buttonReset,
width: '100%',
height: '100%',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
padding: '12px',
textAlign: 'start',
fontSize: 'inherit',
color: 'inherit',
}"
type="button"
@click="collapsed = true"
>
<span style="display: flex; align-items: center; justify-content: space-between; padding: 5px 10px; column-gap: 10px; color: var(--semi-color-text-1)">
<span style="font-size: 14px; font-weight: 500">Source</span>
<IconChevronUp />
</span>
<span style="display: flex; flex-wrap: wrap; gap: 10px; overflow: scroll; padding: 5px 10px">
<span v-for="s in sourcesOf(item)" :key="s.url" style="display: flex; flex-direction: column; row-gap: 5px; flex-basis: 150px; flex-grow: 1; border: 1px solid var(--semi-color-border); border-radius: 12px; padding: 12px; font-size: 12px">
<span style="display: flex; column-gap: 5px; align-items: center">
<Avatar alt="" shape="square" :src="s.avatar" style="width: 16px; height: 16px; flex-shrink: 0" />
<span style="color: var(--semi-color-text-2); text-overflow: ellipsis">
{{ s.title }}
</span>
</span>
<span style="color: var(--semi-color-primary); font-size: 12px">{{ s.subTitle }}</span>
<span style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; text-overflow: ellipsis; overflow: hidden; color: var(--semi-color-text-2)">
{{ s.content }}
</span>
</span>
</span>
</button>
</div>
<MarkdownRender :raw="typeof item.content === 'string' ? item.content : ''" />
</div>
</template>
</Chat>
</template>Use the #fullChatBox slot to custom render the entire chat box
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage } from '@fibrous/semi-ai-vue';
import { Avatar } from '@fibrous/semi-ui-vue';
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
const titleStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
columnGap: '10px',
padding: '5px 0px',
width: 'fit-content',
};
let id = 0;
const getId = () => `id-${id++}`;
const message = ref<ChatMessage[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: '介绍一下 semi design',
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
},
]);
const onMessageSend = () => {
const newAssistantMessage: ChatMessage = {
role: 'assistant',
id: getId(),
content: 'This is a mock response',
};
setTimeout(() => {
message.value = [...message.value, newAssistantMessage];
}, 200);
};
</script>
<template>
<Chat v-model:chats="message" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
<template #fullChatBox="{ role, message: item, defaultNodes, class: boxClass }">
<div :class="boxClass">
<div
:style="{
display: 'flex',
flexDirection: 'column',
rowGap: '4px',
alignItems: item.role === 'user' ? 'end' : undefined,
}"
>
<span v-if="item.role !== 'user'" :style="titleStyle">
<Avatar alt="" shape="square" size="extra-small" :src="role.avatar" />
<component :is="defaultNodes.title" />
</span>
<div style="width: fit-content">
<component :is="defaultNodes.content" />
</div>
<component :is="defaultNodes.action" />
</div>
</div>
</template>
</Chat>
</template>Custom render InputArea
The input box can be customized through the #inputArea slot, the slot parameters are as follows
export interface ChatInputAreaSlotProps {
/* Default node */
defaultNode: VNode;
/* clear context button */
clearContextNode: VNode;
/* upload button, null when every upload way is off */
uploadNode: VNode | null;
/* text input area */
inputNode: VNode;
/* send button */
sendNode: VNode;
/* If you customize the input box, you need to call it when sending a message. */
onSend: (content?: string, attachment?: FileItem[]) => void;
/* If you customize the clear context button, it needs to be called when you click to clear the context */
onClear: () => void;
/* The processing function that triggers the focus of the text input box after clicking */
onClick: () => void;
}
Example of the part nodes
<template>
<Chat v-model:chats="chats">
<template #inputArea="{ uploadNode, inputNode, sendNode, onClick }">
<div
style="
margin: 8px 16px;
display: flex;
flex-direction: row;
align-items: flex-end;
border-radius: 16px;
padding: 10px;
border: 1px solid var(--semi-color-border);
"
@click="onClick"
>
<component :is="uploadNode" />
<component :is="inputNode" />
<component :is="sendNode" />
</div>
</template>
</Chat>
</template>
Other Example:
Hello, I'm your AI assistant.
Code
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
import { IconUpload } from '@fibrous/semi-icons-vue';
import { Button, Form, FormInput, FormUpload, type FileItem, type FormApi } from '@fibrous/semi-ui-vue';
interface InputValues {
name?: string;
file?: FileItem[];
}
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '500px',
};
const inputStyle = {
display: 'flex',
flexDirection: 'column',
border: '1px solid var(--semi-color-border)',
margin: '8px 16px',
borderRadius: '8px',
padding: '8px',
} as const;
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const messages = ref<Message[]>([
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
]);
type Send = (content?: string, attachment?: FileItem[]) => void;
const formApi = shallowRef<FormApi<InputValues>>();
const setFormApi = (api: FormApi<InputValues>) => {
formApi.value = api;
};
const onMessageSend = () => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
messages.value = [...messages.value, newAssistantMessage];
}, 200);
};
const submit = (onSend: Send) => {
const api = formApi.value;
if (!api) {
return;
}
const values = api.getValues();
if (values.name?.length || values.file?.length) {
onSend(values.name, values.file);
api.reset();
}
};
</script>
<template>
<Chat v-model:chats="messages" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend">
<template #inputArea="area">
<div :style="inputStyle">
<Form @getFormApi="setFormApi">
<strong>输入信息</strong>
<FormInput field="name" label="名称(Input)" :style="{ width: '250px' }" trigger="blur" />
<FormUpload :action="uploadProps.action" field="file" label="文档">
<Button theme="light">
<template #icon>
<IconUpload />
</template>
点击上传
</Button>
</FormUpload>
</Form>
<Button :style="{ width: 'fit-content' }" @click="submit(area.onSend)">提交</Button>
</div>
</template>
</Chat>
</template>Hint
The prompt area content can be set through hints. After clicking the prompt content, the prompt content will become the new user input content and trigger the hintClick event.
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
const hintsExample = ['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?'];
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const messages = ref<Message[]>([
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
]);
const hints = ref(hintsExample);
const onMessageSend = () => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
messages.value = [...messages.value, newAssistantMessage];
}, 200);
};
</script>
<template>
<Chat v-model:chats="messages" :hints="hints" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @clear="hints = []" @hintClick="hints = []" @messageSend="onMessageSend" />
</template>Custom render Hint
Customize the content of the prompt area through the #hintBox slot, the parameters are as follows
export interface ChatHintBoxSlotProps {
content: string;
index: number;
onHintClick: () => void;
}
Example:
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
Code
<script setup lang="ts">
import { ref } from 'vue';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-vue';
import { IconArrowRight } from '@fibrous/semi-icons-vue';
const hintsExample = ['告诉我更多', 'Semi Design 的组件有哪些?', '我能够通过 DSM 定制自己的主题吗?'];
const roleInfo = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
height: '400px',
};
const commonHintStyle = {
border: '1px solid var(--semi-color-border)',
padding: '10px',
borderRadius: '10px',
background: 'none',
color: 'var( --semi-color-text-1)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
textAlign: 'left',
cursor: 'pointer',
fontFamily: 'inherit',
fontSize: '14px',
lineHeight: 'inherit',
} as const;
const uploadProps = { action: 'https://api.semi.design/upload' };
let id = 0;
const getId = () => `id-${id++}`;
const messages = ref<Message[]>([
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
]);
const hints = ref(hintsExample);
const onMessageSend = () => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
messages.value = [...messages.value, newAssistantMessage];
}, 200);
hints.value = [];
};
</script>
<template>
<Chat v-model:chats="messages" :hints="hints" :roleConfig="roleInfo" :style="commonOuterStyle" :uploadProps="uploadProps" @clear="hints = []" @hintClick="hints = []" @messageSend="onMessageSend">
<template #hintBox="{ content, onHintClick }">
<button :style="commonHintStyle" type="button" @click="onHintClick">
{{ content }}
<IconArrowRight :style="{ marginLeft: '10px' }" />
</button>
</template>
</Chat>
</template>API Reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| chats | The conversation list, used with v-model:chats. Only replacing the whole array counts as a change; pushing into the same array does not trigger scroll following. | Message[] | semiGlobalDefault("Chat", "chats") |
| align | Conversation layout: leftRight or leftAlign. | ChatAlign | semiDefault("Chat", "align", "leftRight") |
| mode | Conversation mode: bubble, noBubble or userBubble. | ChatMode | semiDefault("Chat", "mode", "bubble") |
| roleConfig | Role information, keyed by the message role. avatar takes an image URL only; use the roleAvatar slot for a node avatar. color is passed to Avatar. | ChatRoleConfig | semiGlobalDefault("Chat", "roleConfig") |
| hints | The list of hints. Clicking one emits hintClick and the hint becomes a new user message. | string[] | semiGlobalDefault("Chat", "hints") |
| sendHotKey | Send hot key: enter or shift+enter. With enter, Enter sends and Shift+Enter inserts a line break; shift+enter is the reverse. | SendHotKey | semiDefault("Chat", "sendHotKey", "enter") |
| placeholder | Placeholder of the input. | string | semiGlobalDefault("Chat", "placeholder") |
| showStopGenerate | Whether to show the stop button while the last message is generating. Sending is unavailable meanwhile, and clicking the stop button emits stopGenerator. | boolean | semiDefault("Chat", "showStopGenerate", false) |
| showClearContext | Whether to show the clear context button in the input. | boolean | semiDefault("Chat", "showClearContext", false) |
| enableUpload | Whether uploading is enabled. A boolean controls paste, click and drag upload together; an object sets pasteUpload, clickUpload and dragUpload separately, and unset items are true. | boolean | EnableUploadProps | semiDefault("Chat", "enableUpload", true) |
| canSend | Whether the send button is usable. The internal logic decides by default; a given value overrides it. | boolean | semiGlobalDefault("Chat", "canSend") |
| escapeHtml | Whether to escape HTML tags in the user's messages, so the Markdown parser does not treat them as HTML and drop the content. | boolean | semiDefault("Chat", "escapeHtml", true) |
| uploadProps | Props passed to Upload. Chat owns fileList and defaultFileList, and Upload events are given as onXxx. Chat handles onChange itself when an attachment is added or removed. | ChatUploadProps | semiGlobalDefault("Chat", "uploadProps") |
| uploadTipProps | Props of the upload button's Tooltip, without content; use the uploadTip slot for the text. | TypeOmit<TooltipProps, "content"> | semiGlobalDefault("Chat", "uploadTipProps") |
| markdownRenderProps | Props passed to the MarkdownRender that renders message content; format defaults to md. raw and components cannot be set; use customMarkDownComponents for custom renderers. | ChatMarkdownRenderProps | semiGlobalDefault("Chat", "markdownRenderProps") |
| customMarkDownComponents | Custom Markdown renderers keyed by element name, passed to the MarkdownRender of message content. | MarkdownRenderComponents | semiGlobalDefault("Chat", "customMarkDownComponents") |
| inputBoxCls | Class name of the input box. | string | semiGlobalDefault("Chat", "inputBoxCls") |
| inputBoxStyle | Style of the input box. | StyleValue | semiGlobalDefault("Chat", "inputBoxStyle") |
| hintCls | Class name of the outermost element of the hint area. | string | semiGlobalDefault("Chat", "hintCls") |
| hintStyle | Style of the outermost element of the hint area. | StyleValue | semiGlobalDefault("Chat", "hintStyle") |
Events
| Event | Description | Callback parameters |
|---|---|---|
| update:chats | Emitted when the conversation list changes, for v-model:chats to write back; fires before chatsChange. | [chats: Message[]] |
| chatsChange | Emitted when the conversation list changes. | [chats: Message[]] |
| messageSend | Emitted when a message is sent, with the text and the attachments. | [content: string, attachment?: FileItem[] | undefined] |
| messageCopy | Emitted when a message is copied. | [message: Message] |
| messageGoodFeedback | Emitted on positive feedback for a message. | [message: Message] |
| messageBadFeedback | Emitted on negative feedback for a message. | [message: Message] |
| messageReset | Emitted when a message is reset; the argument is null when it comes from the resetMessage method. | [message: Message | null] |
| messageDelete | Emitted when a message is deleted. | [message: Message] |
| clear | Emitted when the context is cleared. | [] |
| hintClick | Emitted when a hint is clicked. | [hint: string] |
| inputChange | Emitted when the input text or the attachments change, with { inputValue, attachment }. | [props: ChatInputChangeProps] |
| stopGenerator | Emitted when the stop button is clicked. | [event: MouseEvent] |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| topSlot | Content above the conversation area. | any |
| bottomSlot | Content below the input box. | any |
| title | Custom message title, with message, role and the default title defaultTitle. Provided but empty, it hides the title. | ChatTitleSlotProps |
| avatar | Custom message avatar, with message, role and the default avatar defaultAvatar. Provided but empty, it hides the avatar. | ChatAvatarSlotProps |
| roleAvatar | Avatar content for a role without an image URL, with message and role. | ChatRoleAvatarSlotProps |
| content | Custom message content area, with message, role, the default content defaultContent and the class name class. | ChatContentSlotProps |
| action | Custom message action bar, with message, the default buttons defaultActions, defaultActionsObj keyed by name and the class name class. | ChatActionSlotProps |
| fullChatBox | Replaces the whole message box, with message, role, the default node of each part defaultNodes and the class name class. | ChatFullChatBoxSlotProps |
| inputArea | Custom input area, with defaultNode, the part nodes (clearContextNode, uploadNode, inputNode, sendNode) and onSend, onClear and onClick for you to call. | ChatInputAreaSlotProps |
| hintBox | Custom hint item, with content, index and the click handler onHintClick. | ChatHintBoxSlotProps |
| divider | Custom divider shown after the context is cleared, with message. | ChatDividerSlotProps |
| uploadButton | Replaces the upload button. | any |
| uploadTip | Text of the upload button's tooltip. | any |
Exposes
| Name | Description | Type |
|---|---|---|
| resetMessage | Resets the last message to an empty loading one and emits messageReset with null. | Type() => void |
| clearContext | Clears the context by appending a divider and emits clear; does nothing when the list already ends with one. | Type() => void |
| scrollToBottom | Scrolls to the bottom; smoothly when animation is true, otherwise jumps. | Type(animation?: boolean | undefined) => void |
| sendMessage | Sends a message; without attachment the message content is a string. | Type(content: string, attachment?: FileItem[] | undefined) => void |
class, style and the other undeclared attributes land on the root element. When the root has aria-label or aria-labelledby and no role, it gets role="group". The dragover, dragstart and dragend listeners also land on the root, and Chat's own drag-upload handling runs before the caller's listener.
RoleConfig
| PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
|---|---|---|---|
| user | User information | ChatMetadata | - |
| assistant | Assistant information | ChatMetadata | - |
| system | System information | ChatMetadata | - |
Metadata
| PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
|---|---|---|---|
| name | name | string | - |
| avatar | avatar image URL. Use the #roleAvatar slot for a node avatar (such as Semi icons) | string | - |
| color | Avatar 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、 yellow | AvatarColor | grey |
Message
| PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
|---|---|---|---|
| role | role | string | - |
| name | name | string | - |
| id | Uniquely identifies | string | - |
| content | all content | string | Content[] | - |
| parentId | parent Uniquely identifies | string | - |
| createAt | creation time | number | - |
| status | Information status, loading | incomplete | complete | error | string | complete |
Content
| PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
|---|---|---|---|
| type | type, support text | image_url | file_url | string | - |
| text | Content data when type is text | string | - |
| image_url | Content data when type is image_url | { url: string } | - |
| file_url | Content data when type is file_url | { url: string; name: string; size: string; type: string } | - |
Accessibility
Keyboard and Focus
- In the input box,
EnterorShift+Entersends the message, as decided bysendHotKey: withenter,Entersends andShift+Enterinserts a line break;shift+enteris the reverse. Shift+Tabin the input box returns to the upload button. The default upload button is a native button:EnterorSpaceopens 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
Taborder, and shows when focus enters one of its buttons. The confirmation opened by the delete button closes withEscand 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 witharia-busy="true"while the last message is generating, so screen readers announce the reply once it completes rather than chunk by chunk.
Design Token
| Sass variable | Value | Usage |
|---|---|---|
| $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 |
