使用场景
Chat 组件可用于普通会话,AI 会话等场景。
对话内容渲染基于 MarkdownRender 组件,支持 Markdown 和 MDX (注:Chat 中的 MarkdownRender 的默认 format 模式是 md,如果需要使用 MDX 格式,可通过 markdownRenderProps API 设置),可实现图片,表格,链接,加粗,代码区等常用富文本功能。也可通过 JSX 实现更加复杂定制化的文档撰写与展示需求。代码区的高亮语言需先通过 CodeHighlight 的 CodeHighlight.register 注册。
代码演示
如何引入
import { Chat } from '@fibrous/semi-ai-react';
基本用法
通过设置 chats 和 onChatsChange,onMessageSend 实现基础对话显示和交互。
附件支持通过点击上传按钮,输入框粘贴,拖拽文件至 Chat 区域上传。通过 uploadProps 设置上传参数,详情参考 Upload。
上传按钮的提示文案可通过 uploadTipProps 设置,详情参考 Tooltip。
对话是多方参与,多轮交互的场景。可通过 roleConfig 传入角色信息(包括名称,头像等),具体参数细节 RoleConfig。
使用 align 属性可以设置对话的布局,支持左右分布(leftRight, 默认)和左对齐(leftAlign)。
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
jsx
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message, type ChatProps } from '@fibrous/semi-ai-react';
import { Radio, RadioGroup, type RadioChangeEvent } from '@fibrous/semi-ui-react';
const defaultMessage: Message[] = [
{
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 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: 550,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const uploadTipProps = { content: '自定义上传按钮提示信息' };
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const [mode, setMode] = useState<NonNullable<ChatProps['mode']>>('bubble');
const [align, setAlign] = useState<NonNullable<ChatProps['align']>>('leftRight');
const onAlignChange = useCallback((e: RadioChangeEvent) => {
setAlign(e.target.value);
}, []);
const onModeChange = useCallback((e: RadioChangeEvent) => {
setMode(e.target.value);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageReset = useCallback(() => {
setTimeout(() => {
setMessage((message) => {
const lastMessage = message[message.length - 1];
const newLastMessage: Message = {
...lastMessage,
status: 'complete',
content: 'This is a mock reset message.',
};
return [...message.slice(0, -1), newLastMessage];
});
}, 200);
}, []);
return (
<>
<span style={{ display: 'flex', flexDirection: 'column', rowGap: '8px' }}>
<span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}>
模式
<RadioGroup onChange={onModeChange} value={mode} type="button">
<Radio value="bubble">气泡</Radio>
<Radio value="noBubble">非气泡</Radio>
<Radio value="userBubble">用户会话气泡</Radio>
</RadioGroup>
</span>
<span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}>
会话布局方式
<RadioGroup onChange={onAlignChange} value={align} type="button">
<Radio value="leftRight">左右分布</Radio>
<Radio value="leftAlign">左对齐</Radio>
</RadioGroup>
</span>
</span>
<Chat key={align + mode} align={align} mode={mode} uploadProps={uploadProps} style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} onMessageReset={onMessageReset} uploadTipProps={uploadTipProps} />
</>
);
}消息状态
chats 类型为 Message[], Message 包含对话的各种信息,如角色(role)、内容(content)、附件(attachment)、状态(status)、唯一标识(id)、创建时间(createAt)等,具体见 Message。其中 status 不同,会话样式不同。
请求成功
请求错误
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-react';
const defaultMessage: Message[] = [
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: '请求成功',
},
{
id: 'loading',
role: 'assistant',
status: 'loading',
},
{
role: 'assistant',
id: 'error',
content: '请求错误',
status: 'error',
},
];
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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
return <Chat style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} uploadProps={uploadProps} />;
}动态更新数据
对于后台返回 Serve Side Event 数据情况,可将获取到的数据用于更新 chats,对话内容将实时更新。
showStopGenerate 参数可用于设置是否展示停止生成按钮,默认为 false。 可以在 onStopGenerator 中处理停止生成逻辑。
Hello, I'm your AI assistant.
介绍一下 Semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户[1]。
Semi Design的特点包括:
- 设计简洁、现代化。
- 提供主题方案,可深度样式定制。
- 提供明暗色两套模式,切换方便。
- 国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
- 采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
Learn more:
代码
import { useState, useCallback, useEffect, useRef } from 'react';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-react';
const defaultMessage: 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)
`,
},
];
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: 600,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const intervalId = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(
() => () => {
if (intervalId.current) clearInterval(intervalId.current);
},
[]
);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const generateMockResponse = useCallback((content: string) => {
const id = setInterval(() => {
setMessage((message) => {
const lastMessage = message[message.length - 1];
let newMessage: Message = { ...lastMessage };
if (lastMessage.status === 'loading') {
newMessage = {
...newMessage,
content: `mock Response for ${content} \n`,
status: 'incomplete',
};
} else if (lastMessage.status === 'incomplete') {
if ((lastMessage.content?.length ?? 0) > 200) {
clearInterval(id);
intervalId.current = null;
newMessage = {
...newMessage,
content: `${lastMessage.content} mock stream message`,
status: 'complete',
};
} else {
newMessage = {
...newMessage,
content: `${lastMessage.content} mock stream message`,
};
}
}
return [...message.slice(0, -1), newMessage];
});
}, 400);
intervalId.current = id;
}, []);
const onMessageSend = useCallback(
(content: string) => {
setMessage((message) => {
return [
...message,
{
role: 'assistant',
status: 'loading',
createAt: Date.now(),
id: getId(),
},
];
});
generateMockResponse(content);
},
[generateMockResponse]
);
const onStopGenerator = useCallback(() => {
if (intervalId.current) {
clearInterval(intervalId.current);
setMessage((message) => {
const lastMessage = message[message.length - 1];
if (lastMessage.status && lastMessage.status !== 'complete') {
let newMessage: Message = { ...lastMessage };
newMessage.status = 'complete';
return [...message.slice(0, -1), newMessage];
} else {
return message;
}
});
}
}, []);
return <Chat chats={message} showStopGenerate style={commonOuterStyle} onStopGenerator={onStopGenerator} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} uploadProps={uploadProps} />;
}清除上下文
通过 showClearContext 可以开启在输入框中显示清除上下文按钮,默认为 false。也可以通过 ref 调用 clearContext 方法清除上下文。
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-react';
const defaultMessage: 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产品设计团队设计、开发并维护的设计系统',
},
];
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: 550,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const uploadTipProps = { content: '自定义上传按钮提示信息' };
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageReset = useCallback(() => {
setTimeout(() => {
setMessage((message) => {
const lastMessage = message[message.length - 1];
const newLastMessage: Message = {
...lastMessage,
status: 'complete',
content: 'This is a mock reset message.',
};
return [...message.slice(0, -1), newLastMessage];
});
}, 200);
}, []);
return (
<>
<Chat uploadProps={uploadProps} style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} onMessageReset={onMessageReset} uploadTipProps={uploadTipProps} showClearContext />
</>
);
}自定义渲染会话框
通过 chatBoxRenderConfig 传入自定义渲染配置, chatBoxRenderConfig 类型如下
export interface RenderTitleProps {
message?: Message;
role?: Metadata;
defaultTitle?: ReactNode;
}
export interface RenderAvatarProps {
message?: Message;
role?: Metadata;
defaultAvatar?: ReactNode;
}
export interface RenderContentProps {
message?: Message;
role?: Metadata;
defaultContent?: ReactNode | ReactNode[];
className?: string;
}
/* 与默认操作栏显示的按钮一致,按钮不显示时(如消息未完成)对应的键不存在 */
export interface DefaultActionNodeObj {
copyNode?: ReactNode;
likeNode?: ReactNode;
dislikeNode?: ReactNode;
resetNode?: ReactNode;
deleteNode?: ReactNode;
}
export interface RenderActionProps {
message?: Message;
defaultActions?: ReactNode | ReactNode[];
className: string;
defaultActionsObj?: DefaultActionNodeObj;
}
export interface FullChatBoxNodes {
avatar?: ReactNode;
title?: ReactNode;
content?: ReactNode;
action?: ReactNode;
}
export interface RenderFullChatBoxProps {
message?: Message;
role?: Metadata;
defaultNodes?: FullChatBoxNodes;
className: string;
}
export interface ChatBoxRenderConfig {
/* 自定义渲染标题 */
renderChatBoxTitle?: (props: RenderTitleProps) => ReactNode;
/* 自定义渲染头像 */
renderChatBoxAvatar?: (props: RenderAvatarProps) => ReactNode;
/* 自定义渲染内容区域 */
renderChatBoxContent?: (props: RenderContentProps) => ReactNode;
/* 自定义渲染消息操作栏 */
renderChatBoxAction?: (props: RenderActionProps) => ReactNode;
/* 完全自定义渲染整个聊天框 */
renderFullChatBox?: (props: RenderFullChatBoxProps) => ReactNode;
}
自定义渲染头像和标题,可通过 renderChatBoxAvatar 和 renderChatBoxTitle 实现。
Hello, I'm your AI assistant.
这张图片里有什么?
图片中是一个有卡通画像装饰的黄色背包。
代码
import { useState, useCallback, useMemo } from 'react';
import { Chat, type ChatMessage as Message, type ChatRenderAvatarProps as RenderAvatarProps, type ChatRenderTitleProps as RenderTitleProps } from '@fibrous/semi-ai-react';
import { Avatar, Radio, RadioGroup, type RadioChangeEvent } from '@fibrous/semi-ui-react';
const defaultMessage: Message[] = [
{
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 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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
export default function ChatDemo() {
const [title, setTitle] = useState('null');
const [avatar, setAvatar] = useState('null');
const [message, setMessage] = useState(defaultMessage);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const customRenderAvatar = useMemo(() => {
switch (avatar) {
case 'custom':
return ({ role }: RenderAvatarProps) => {
return (
<Avatar size="extra-small" shape="square" style={{ flexShrink: '0' }}>
{role?.name}
</Avatar>
);
};
case 'null':
return () => null;
case 'default':
return undefined;
}
}, [avatar]);
const customRenderTitle = useMemo(() => {
switch (title) {
case 'custom':
return ({ role, message }: RenderTitleProps) => {
const date = new Date(message?.createAt ?? Date.now());
const hours = ('0' + date.getHours()).slice(-2);
const minutes = ('0' + date.getMinutes()).slice(-2);
const formatTime = `${hours}:${minutes}`;
return (
<span className="title">
{role?.name}
<span className="time">{formatTime}</span>
</span>
);
};
case 'null':
return () => null;
case 'default':
return undefined;
}
}, [title]);
const onAvatarChange = useCallback((e: RadioChangeEvent) => {
setAvatar(e.target.value);
}, []);
const onTitleChange = useCallback((e: RadioChangeEvent) => {
setTitle(e.target.value);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
return (
<>
<span style={{ display: 'flex', flexDirection: 'column', rowGap: 8, marginBottom: 5 }}>
<span style={{ display: 'flex', alignItems: 'center', columnGap: 10 }}>
头像渲染模式
<RadioGroup onChange={onAvatarChange} value={avatar} type="button">
<Radio value="default">默认头像</Radio>
<Radio value="null">无头像</Radio>
<Radio value="custom">自定义头像</Radio>
</RadioGroup>
</span>
<span style={{ display: 'flex', alignItems: 'center', columnGap: 10 }}>
标题渲染模式
<RadioGroup onChange={onTitleChange} value={title} type="button">
<Radio value="default">默认标题</Radio>
<Radio value="null">无标题</Radio>
<Radio value="custom">自定义标题</Radio>
</RadioGroup>
</span>
</span>
<Chat
chatBoxRenderConfig={{
renderChatBoxTitle: customRenderTitle,
renderChatBoxAvatar: customRenderAvatar,
}}
key={`${avatar}${title}`}
style={commonOuterStyle}
className="component-chat-demo-custom-render"
chats={message}
onChatsChange={onChatsChange}
onMessageSend={onMessageSend}
roleConfig={roleInfo}
uploadProps={uploadProps}
/>
</>
);
}鼠标移动到会话上,即可显示会话操作区,通过 renderChatBoxAction 自定义渲染操作区
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
代码
import React, { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message, type ChatRenderActionProps as RenderActionProps } from '@fibrous/semi-ai-react';
import { IconForward, IconMoreStroked } from '@fibrous/semi-icons-react';
import { Dropdown, Button } from '@fibrous/semi-ui-react';
const defaultMessage: 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产品设计团队设计、开发并维护的设计系统',
},
];
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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const CustomActions = React.memo(({ defaultActions, className }: RenderActionProps) => {
return (
<span className={className}>
{defaultActions}
<Dropdown
key="dropdown"
render={
<Dropdown.Menu>
<Dropdown.Item icon={<IconForward />}>分享</Dropdown.Item>
</Dropdown.Menu>
}
trigger="click"
position="top"
>
<Button className="semi-chat-chatBox-action-btn" aria-label="更多" icon={<IconMoreStroked />} theme="borderless" type="tertiary" />
</Dropdown>
</span>
);
});
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const customRenderAction = useCallback((props: RenderActionProps) => {
return <CustomActions {...props} />;
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
return (
<Chat
chatBoxRenderConfig={{
renderChatBoxAction: customRenderAction,
}}
style={commonOuterStyle}
chats={message}
onChatsChange={onChatsChange}
onMessageSend={onMessageSend}
roleConfig={roleInfo}
uploadProps={uploadProps}
/>
);
}通过 renderChatBoxContent 自定义内容区域
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message, type ChatRenderContentProps as RenderContentProps } from '@fibrous/semi-ai-react';
import { IconChevronUp } from '@fibrous/semi-icons-react';
import { MarkdownRender, AvatarGroup, Avatar } from '@fibrous/semi-ui-react';
interface Source {
avatar: string;
url: string;
title: string;
subTitle: string;
content: string;
}
const defaultMessage: Message[] = [
{
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 团队推出的全新的提效工具',
},
],
},
];
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: 500,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const SourceCard = ({ source }: { source: Source[] }) => {
const [open, setOpen] = useState(true);
const onOpen = useCallback(() => {
setOpen(false);
}, []);
const onClose = useCallback(() => {
setOpen(true);
}, []);
return (
<div
style={{
transition: open ? 'height 0.4s ease, width 0.4s ease' : 'height 0.4s ease',
height: open ? '30px' : '200px',
width: open ? '190px' : '100%',
background: 'var(--semi-color-tertiary-light-hover)',
borderRadius: 16,
boxSizing: 'border-box',
marginBottom: 10,
}}
>
<button
type="button"
style={{
display: !open ? 'none' : 'flex',
width: 'fit-content',
columnGap: 10,
border: 'none',
background: 'var(--semi-color-tertiary-light-hover)',
borderRadius: '16px',
padding: '5px 10px',
cursor: 'pointer',
fontFamily: 'inherit',
fontSize: 14,
lineHeight: 'inherit',
color: 'var(--semi-color-text-1)',
}}
onClick={onOpen}
>
<span>基于{source.length}个搜索来源</span>
<AvatarGroup size="extra-extra-small">
{source.map((s, index) => (
<Avatar key={index} src={s.avatar} alt="" />
))}
</AvatarGroup>
</button>
<button
type="button"
style={{
width: '100%',
height: '100%',
boxSizing: 'border-box',
display: !open ? 'flex' : 'none',
flexDirection: 'column',
border: 'none',
background: 'var(--semi-color-tertiary-light-hover)',
borderRadius: '16px',
padding: 12,
textAlign: 'left',
fontFamily: 'inherit',
fontSize: 'inherit',
lineHeight: 'inherit',
color: 'inherit',
}}
onClick={onClose}
>
<span
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '5px 10px',
columnGap: 10,
color: 'var(--semi-color-text-1)',
}}
>
<span style={{ fontSize: 14, fontWeight: 500 }}>Source</span>
<IconChevronUp />
</span>
<span style={{ display: 'flex', flexWrap: 'wrap', gap: 10, overflow: 'scroll', padding: '5px 10px' }}>
{source.map((s) => (
<span
key={s.url}
style={{
display: 'flex',
flexDirection: 'column',
rowGap: 5,
flexBasis: 150,
flexGrow: 1,
border: '1px solid var(--semi-color-border)',
borderRadius: 12,
padding: 12,
fontSize: 12,
}}
>
<span style={{ display: 'flex', columnGap: 5, alignItems: 'center' }}>
<Avatar style={{ width: 16, height: 16, flexShrink: 0 }} shape="square" src={s.avatar} alt="" />
<span style={{ color: 'var(--semi-color-text-2)', textOverflow: 'ellipsis' }}>{s.title}</span>
</span>
<span
style={{
color: 'var(--semi-color-primary)',
fontSize: 12,
}}
>
{s.subTitle}
</span>
<span
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: '3',
textOverflow: 'ellipsis',
overflow: 'hidden',
color: 'var(--semi-color-text-2)',
}}
>
{s.content}
</span>
</span>
))}
</span>
</button>
</div>
);
};
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const renderContent = useCallback(({ message, className }: RenderContentProps) => {
return (
<div className={className}>
{message?.source && <SourceCard source={message.source} />}
<MarkdownRender raw={typeof message?.content === 'string' ? message.content : ''} />
</div>
);
}, []);
return <Chat style={commonOuterStyle} chats={message} roleConfig={roleInfo} chatBoxRenderConfig={{ renderChatBoxContent: renderContent }} onChatsChange={onChatsChange} onMessageSend={onMessageSend} uploadProps={uploadProps} />;
}使用 renderFullChatBox 自定义渲染整个会话框
Hello, I'm your AI assistant.
介绍一下 semi design
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message, type ChatRenderFullChatBoxProps as RenderFullChatBoxProps } from '@fibrous/semi-ai-react';
import { Avatar } from '@fibrous/semi-ui-react';
const defaultMessage: 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产品设计团队设计、开发并维护的设计系统',
},
];
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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const titleStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
columnGap: '10px',
padding: '5px 0px',
width: 'fit-content',
};
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const customRenderChatBox = useCallback(({ role, message, defaultNodes, className }: RenderFullChatBoxProps) => {
let titleNode = null;
if (message?.role !== 'user') {
titleNode = (
<span style={titleStyle}>
<Avatar size="extra-small" shape="square" src={typeof role?.avatar === 'string' ? role.avatar : undefined} alt="" />
{defaultNodes?.title}
</span>
);
}
return (
<div className={className}>
<div
style={{
display: 'flex',
flexDirection: 'column',
rowGap: 4,
alignItems: message?.role === 'user' ? 'end' : '',
}}
>
{titleNode}
<div style={{ width: 'fit-content' }}>{defaultNodes?.content}</div>
{defaultNodes?.action}
</div>
</div>
);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
return <Chat chatBoxRenderConfig={{ renderFullChatBox: customRenderChatBox }} style={commonOuterStyle} chats={message} onChatsChange={onChatsChange} onMessageSend={onMessageSend} roleConfig={roleInfo} uploadProps={uploadProps} />;
}自定义渲染输入框
可通过 renderInputArea 自定义渲染输入框,参数如下
export interface RenderInputAreaProps {
/* 默认节点 */
defaultNode?: ReactNode;
/* 如果自定义输入框,发送消息时需调用 */
onSend?: (content?: string, attachment?: FileItem[]) => void;
/* 如果自定义清除上下文按钮,点击清除上下文时需调用 */
onClear?: (e?: any) => void;
detailProps: {
/* 清除上下文按钮 */
clearContextNode?: ReactNode;
/* 上传按钮 */
uploadNode?: ReactNode;
/* 文本输入框 */
inputNode?: ReactNode;
/* 发送按钮 */
sendNode?: ReactNode;
/* 点击触发聚焦文本输入框的处理函数*/
onClick?: () => void; // 聚焦输入框
};
}
detailProps 的使用示例如下
function CustomInputRender(props) {
const { detailProps } = props;
const { clearContextNode, uploadNode, inputNode, sendNode, onClick } = detailProps;
return (
<div
style={{
margin: '8px 16px',
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
borderRadius: 16,
padding: 10,
border: '1px solid var(--semi-color-border)',
}}
onClick={onClick}
>
{uploadNode}
{inputNode}
{sendNode}
</div>
);
}
其他使用示例如下
Hello, I'm your AI assistant.
代码
import { useState, useCallback, useRef, type CSSProperties } from 'react';
import { Chat, type ChatMessage as Message, type ChatRenderInputAreaProps as RenderInputAreaProps } from '@fibrous/semi-ai-react';
import { IconUpload } from '@fibrous/semi-icons-react';
import { Form, Button, type FormApi as BaseFormApi, type FileItem } from '@fibrous/semi-ui-react';
interface InputValues {
name?: string;
file?: FileItem[];
}
const defaultMessage: Message[] = [
{
role: 'system',
id: '1',
createAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
];
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: 500,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const inputStyle: CSSProperties = {
display: 'flex',
flexDirection: 'column',
border: '1px solid var(--semi-color-border)',
margin: '8px 16px',
borderRadius: 8,
padding: 8,
};
function CustomInputRender({ onSend }: RenderInputAreaProps) {
const api = useRef<BaseFormApi<InputValues> | null>(null);
const onSubmit = useCallback(() => {
if (api.current) {
const values = api.current.getValues();
if ((values.name && values.name.length !== 0) || (values.file && values.file.length !== 0)) {
onSend?.(values.name, values.file);
api.current.reset();
}
}
}, [onSend]);
return (
<div style={inputStyle}>
<Form getFormApi={(formApi) => (api.current = formApi)}>
<strong>输入信息</strong>
<Form.Input field="name" label="名称(Input)" style={{ width: 250 }} trigger="blur" />
<Form.Upload field="file" label="文档" action="https://api.semi.design/upload">
<Button icon={<IconUpload />} theme="light">
点击上传
</Button>
</Form.Upload>
</Form>
<Button style={{ width: 'fit-content' }} onClick={onSubmit}>
提交
</Button>
</div>
);
}
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
content: `This is a mock response`,
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const renderInputArea = useCallback((props?: RenderInputAreaProps) => {
return <CustomInputRender {...props} />;
}, []);
return <Chat renderInputArea={renderInputArea} style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} uploadProps={uploadProps} />;
}提示信息
通过 hints 可设置提示区域内容, 点击提示内容后,提示内容将成为新的用户输入内容,并触发 onHintClick 回调。
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
代码
import { useState, useCallback } from 'react';
import { Chat, type ChatMessage as Message } from '@fibrous/semi-ai-react';
const defaultMessage: Message[] = [
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
];
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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const [hints, setHints] = useState(hintsExample);
const onHintClick = useCallback(() => {
setHints([]);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const onClear = useCallback(() => {
setHints([]);
}, []);
return <Chat hints={hints} onHintClick={onHintClick} style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} onClear={onClear} uploadProps={uploadProps} />;
}自定义提示信息渲染
通过 renderHintBox 自定义提示区域内容, 参数如下
type renderHintBox = (props: { content: string; index: number; onHintClick: () => void }) => React.ReactNode;
使用示例如下:
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
代码
import { useState, useCallback, type CSSProperties } from 'react';
import { Chat, type ChatMessage as Message, type ChatProps } from '@fibrous/semi-ai-react';
import { IconArrowRight } from '@fibrous/semi-icons-react';
const defaultMessage: Message[] = [
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
];
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: 400,
};
let id = 0;
function getId() {
return `id-${id++}`;
}
const uploadProps = { action: 'https://api.semi.design/upload' };
const commonHintStyle: CSSProperties = {
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',
};
export default function ChatDemo() {
const [message, setMessage] = useState(defaultMessage);
const [hints, setHints] = useState(hintsExample);
const onHintClick = useCallback(() => {
setHints([]);
}, []);
const onMessageSend = useCallback(() => {
const newAssistantMessage: Message = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '这是一条 mock 回复信息',
};
setTimeout(() => {
setMessage((message) => [...message, newAssistantMessage]);
}, 200);
setHints([]);
}, []);
const onChatsChange = useCallback((chats: Message[]) => {
setMessage(chats);
}, []);
const renderHintBox = useCallback<NonNullable<ChatProps['renderHintBox']>>(({ content, onHintClick, index }) => {
return (
<button type="button" style={commonHintStyle} onClick={onHintClick} key={index}>
{content}
<IconArrowRight style={{ marginLeft: 10 }} />
</button>
);
}, []);
const onClear = useCallback(() => {
setHints([]);
}, []);
return <Chat renderHintBox={renderHintBox} hints={hints} onHintClick={onHintClick} style={commonOuterStyle} chats={message} roleConfig={roleInfo} onChatsChange={onChatsChange} onMessageSend={onMessageSend} onClear={onClear} uploadProps={uploadProps} />;
}API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| align | 对话布局方式,可选 leftRight(左右分布)或 leftAlign(左对齐)。 | ChatAlign | "leftRight" |
| bottomSlot | 输入框下方的节点。 | ReactNode | ReactNode[] | - |
| canSend | 发送按钮是否可用。通常由内部逻辑决定,设置后以此值为准。 | boolean | - |
| chatBoxRenderConfig | 消息框各部分的自定义渲染函数,类型见 ChatBoxRenderConfig。 | ChatBoxRenderConfig | - |
| chats | 受控的对话列表,配合 onChatsChange 回写。应整体替换数组,而不是就地修改同一数组。 | Message[] | - |
| className | 根元素的类名,与组件自身的类名合并。 | string | - |
| customMarkDownComponents | 自定义 Markdown 渲染组件,键为元素名,透传给消息内容的 MarkdownRender。 | OriginMarkdownRenderProps['components'] | - |
| enableUpload | 是否启用上传。布尔值同时控制粘贴、点击和拖拽上传;传对象时分别设置 pasteUpload、clickUpload、dragUpload,未设置的项为 true。未设置本属性时三种上传都启用。 | boolean | EnableUploadProps | - |
| escapeHtml | 是否转义用户消息中的 HTML 标签,防止被 Markdown 解析器当作 HTML 处理导致内容丢失。只作用于用户消息。 | boolean | true |
| hintCls | 提示区最外层的类名。 | string | - |
| hints | 提示信息列表。点击某项后,该项作为新的用户消息加入对话,并触发 onHintClick。 | string[] | - |
| hintStyle | 提示区最外层的样式。 | React.CSSProperties | - |
| inputBoxCls | 输入框的类名。 | string | - |
| inputBoxStyle | 输入框的样式。 | React.CSSProperties | - |
| markdownRenderProps | 传给消息内容所用 MarkdownRender 的属性,默认 format 为 md。raw 与 components 由 Chat 设置,自定义渲染组件使用 customMarkDownComponents。 | MarkdownRenderProps | - |
| mode | 对话模式,可选 bubble、noBubble 或 userBubble。 | ChatMode | "bubble" |
| onChatsChange | 对话列表变化时触发,参数为新的列表。 | Type(chats: Message[]) => void | - |
| onClear | 清除上下文时触发。 | Type() => void | - |
| onHintClick | 点击提示信息时触发,参数为提示文本。 | Type(hint: string) => void | - |
| onInputChange | 输入内容或附件变化时触发,参数为 inputValue 与 attachment。 | Type(props: { inputValue: string; attachment: FileItem[] }) => void | - |
| onMessageBadFeedback | 消息负向反馈时触发,参数为消息。 | Type(message?: Message) => void | - |
| onMessageCopy | 复制消息时触发,参数为消息。 | Type(message?: Message) => void | - |
| onMessageDelete | 删除消息时触发,参数为消息。 | Type(message?: Message) => void | - |
| onMessageGoodFeedback | 消息正向反馈时触发,参数为消息。 | Type(message?: Message) => void | - |
| onMessageReset | 重置消息时触发;由 resetMessage 方法触发时参数为 null。 | Type(message?: Message) => void | - |
| onMessageSend | 发送消息时触发,参数为文本内容和附件。 | Type(content: string, attachment?: FileItem[]) => void | - |
| onStopGenerator | 点击停止生成按钮时触发,参数为鼠标事件。 | Type(e: React.MouseEvent) => void | - |
| placeholder | 输入框占位符。 | string | - |
| ref | 组件句柄,不是 DOM 元素,带有下方 Methods 中的方法。 | TypeReact.Ref<ChatHandle> | - |
| renderDivider | 自定义清除上下文后的分割线,参数为分割线对应的消息,返回节点。每次渲染时为每条分割线调用。 | Type(message?: Message) => ReactNode | - |
| renderHintBox | 自定义提示项的渲染,参数为 content、index 和 onHintClick,返回节点。 | Type(props: { content: string; index: number; onHintClick: () => void }) => React.ReactNode | - |
| renderInputArea | 自定义输入区域,参数为 defaultNode、onSend、onClear 和 detailProps,返回节点。 | Type(props?: RenderInputAreaProps) => ReactNode | - |
| roleConfig | 角色信息,键为消息的 role。avatar 可以是图片 URL 或节点,color 传给 Avatar。 | RoleConfig | - |
| sendHotKey | 发送快捷键,可选 enter 或 shift+enter。enter 时单独按 Enter 发送,Shift + Enter 换行;shift+enter 相反。 | SendHotKey | "enter" |
| showClearContext | 是否在输入框中显示清除上下文按钮。 | boolean | false |
| showStopGenerate | 是否在最后一条消息生成中时显示停止生成按钮。生成中发送按钮不可用,点击停止按钮触发 onStopGenerator。 | boolean | false |
| style | 根元素的样式。 | React.CSSProperties | - |
| topSlot | 对话区域顶部的节点。 | ReactNode | ReactNode[] | - |
| uploadProps | 传给 Upload 的属性。fileList 与 defaultFileList 由 Chat 管理,onChange 由 Chat 处理,不会传给 Upload;children 替换默认的上传按钮,作为触发元素。 | TypeOmit<UploadProps, 'fileList' | 'defaultFileList'> | - |
| uploadTipProps | 上传按钮提示 Tooltip 的属性,content 为提示文字。 | TooltipProps | - |
Methods
| 方法 | 说明 | 类型 |
|---|---|---|
| clearContext | 在对话末尾追加分割线以清除上下文,并触发 onClear;末尾已是分割线时无效果。 | Type() => void |
| resetMessage | 将最后一条消息重置为空内容的 loading 状态,并触发 onMessageReset,参数为 null。 | Type() => void |
| scrollToBottom | 滚动到最底部,animation 为 true 时平滑滚动,否则直接跳转。 | Type(animation?: boolean) => void |
| sendMessage | 发送消息,不传 attachment 时消息的内容为字符串。 | Type(content: string, attachment?: FileItem[]) => void |
其他未声明的原生属性(如 data-*、id)落在根元素 div 上;根元素带有 aria-label 或 aria-labelledby 且没有传 role 时,自动加上 role="group"。onDragOver、onDragStart、onDragEnd 监听同样落在根元素上,Chat 自己的拖拽上传处理先于使用方的监听执行。
RoleConfig
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| user | 用户信息 | Metadata | - |
| assistant | 助手信息 | Metadata | - |
| system | 系统信息 | Metadata | - |
Metadata
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| name | 名称 | string | - |
| avatar | 头像,支持图片 URL 或 ReactNode(如 Semi 图标) | ReactNode | 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 |
