Semi AI 组件是一套专为 AI 应用场景设计的创新组件库。面对 AI 正在成为产品主角的时代,传统的设计体系已难以支撑日益复杂的智能交互,Semi AI 组件以「人机智能协同」为核心理念,致力于让用户与 AI 系统协作更高效、可控、可感知、可信赖。Semi AI 组件库包含以下内容:AI Token、AI 基础组件以及 AI Chat 组件,用于服务智能问答与助手、多模态输入与多轮对话等场景。
AI Token, 新增加 AI 场景下的基础 20 个颜色的基础 tokenAI 基础组件, 新增加 AI 场景下的 25 个 icon,以及对Button/Tag/FloatButton新增 AI 风格AI Chat 组件,新增加支持富文本输入、引用,上传、功能配置、及丰富自定义展示的AIChatInput;以及会话展示、选择、编辑、提示、丰富自定义渲染、消息转换的AIChatDialogue,对话组件默认支持 OpenAI 社区 Response / Chat Completion 格式标准,对 GPT-5、GPT-4o 系列模型的响应均支持开箱即用,详见消息数据转换。以及具备产物编辑、产物查看、引用资料展示等能力的多功能侧边栏组件Sidebar。(关于Chat组件和AI Chat组件如何选型见 FAQ)
在未来,我们将支持更多 AI Chat 组件,比如结合 AIChatInput 和 AIChatDialogue 的一体化组件 AIChatBox。用于满足复杂 AI 应用场景下的信息与结果管理需求。
如何引入
AI Chat 组件在独立的 AI 包中,Button、Tag、FloatButton 等基础组件来自 UI 包,图标来自图标包。
pnpm add @fibrous/semi-ai-vue
<script setup lang="ts">
import { AIChatInput, AIChatDialogue, Chat, Sidebar } from '@fibrous/semi-ai-vue';
import { Button, Tag, FloatButton } from '@fibrous/semi-ui-vue';
import { IconAIFilledLevel1 } from '@fibrous/semi-icons-vue';
</script>
AI 基础组件
AI 基础组件包括 AI Icon、AI 风格的 Button / Tag / FloatButton, AI 基础组件的 AI 风格是在 AI Token 的基础上实现。
对于 AI Token,我们构建了一套以紫蓝渐变系为核心的品牌色板,在基础色中,新增加了 AI purple 和 AI general 两个色相,20 个颜色的色盘。
对于 AI Icon,支持 Icon 场景下的单色、双色、及多色 Icon,共 25 个图标。
对于 AI 风格的 Button / Tag / FloatButton,可通过组件的 colorful 属性开启。
以下是 AI 基础组件的一些示例,更多示例及使用场景详见 AI Token、AI Icon、AI Button、AI Tag、AI FloatButton。
AI Token
AI Icon
AI Button
AI Tag
AI FloatButton
代码
<script setup lang="ts">
import { IconAIBellLevel1, IconAIEditLevel1, IconAIEditLevel2, IconAIFileLevel3, IconAIFilledLevel1, IconAIFilledLevel3, IconAIImageLevel3, IconAILoading, IconAISearchLevel3, IconAIStrokedLevel3, IconAIWandLevel3 } from '@fibrous/semi-icons-vue';
import { Button, FloatButton, Tag, TypographyTitle } from '@fibrous/semi-ui-vue';
const wandFill = ['var(--semi-color-danger)', 'var(--semi-color-success)', 'var(--semi-color-primary)', 'var(--semi-color-warning)'];
const colorStyle = (color: string) => ({
width: '40px',
height: '40px',
background: color,
borderRadius: '50%',
});
</script>
<template>
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '20px' }">
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '10px' }">
<TypographyTitle :heading="5">AI Token</TypographyTitle>
<div :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
<span>Purple</span>
<div :style="{ display: 'inline-flex', columnGap: '4px' }">
<div v-for="index in 10" :key="index" :style="colorStyle(`var(--semi-ai-purple-${index - 1})`)" />
</div>
</div>
<div :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
<span>General</span>
<div :style="{ display: 'inline-flex', columnGap: '4px' }">
<div v-for="index in 10" :key="index" :style="colorStyle(`var(--semi-ai-general-${index - 1})`)" />
</div>
</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '10px' }">
<TypographyTitle :heading="5">AI Icon</TypographyTitle>
<div :style="{ display: 'flex', columnGap: '10px' }">
<IconAIBellLevel1 size="extra-large" />
<IconAIEditLevel2 size="extra-large" />
<IconAIFileLevel3 size="extra-large" />
<IconAIFilledLevel3 size="extra-large" />
<IconAIImageLevel3 size="extra-large" />
<IconAISearchLevel3 size="extra-large" />
<IconAIStrokedLevel3 size="extra-large" />
<IconAIWandLevel3 :fill="wandFill" size="extra-large" />
<IconAILoading size="extra-large" />
</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '10px' }">
<TypographyTitle :heading="5">AI Button</TypographyTitle>
<div :style="{ display: 'flex', rowGap: '16px', flexDirection: 'column' }">
<div :style="{ display: 'flex', columnGap: '16px' }">
<Button colorful theme="solid" type="primary">
<template #icon>
<IconAIFilledLevel1 />
</template>
Colorful
</Button>
<Button colorful loading theme="solid" type="primary">Colorful</Button>
<Button colorful disabled theme="solid" type="primary">
<template #icon>
<IconAIFilledLevel1 />
</template>
Colorful
</Button>
</div>
<div :style="{ display: 'flex', columnGap: '16px' }">
<Button colorful theme="solid" type="tertiary">
<template #icon>
<IconAIFilledLevel3 />
</template>
Colorful
</Button>
<Button colorful loading theme="solid" type="tertiary">Colorful</Button>
<Button colorful disabled theme="solid" type="tertiary">
<template #icon>
<IconAIFilledLevel3 />
</template>
Colorful
</Button>
</div>
<div :style="{ display: 'flex', columnGap: '16px' }">
<Button aria-label="AI" colorful theme="solid" type="primary">
<template #icon>
<IconAIFilledLevel1 />
</template>
</Button>
<Button aria-label="AI" colorful loading theme="solid" type="primary" />
<Button aria-label="AI" colorful disabled theme="solid" type="primary">
<template #icon>
<IconAIFilledLevel1 />
</template>
</Button>
<Button aria-label="AI" colorful theme="solid" type="tertiary">
<template #icon>
<IconAIFilledLevel3 />
</template>
</Button>
<Button aria-label="AI" colorful loading theme="solid" type="tertiary" />
<Button aria-label="AI" colorful disabled theme="solid" type="tertiary">
<template #icon>
<IconAIFilledLevel3 />
</template>
</Button>
</div>
</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '10px' }">
<TypographyTitle :heading="5" :style="{ marginTop: '10px' }">AI Tag</TypographyTitle>
<div
:style="{
display: 'grid',
gridTemplateColumns: 'repeat(3,1fr)',
gridGap: '10px',
width: 'fit-content',
}"
>
<Tag colorful gradient shape="circle" type="solid">
<template #prefixIcon>
<IconAIFilledLevel1 size="small" />
</template>
AI
</Tag>
<Tag colorful gradient shape="circle" type="light">
<template #prefixIcon>
<IconAIFilledLevel3 size="small" />
</template>
AI
</Tag>
<Tag colorful gradient shape="circle" type="ghost">
<template #prefixIcon>
<IconAIFilledLevel3 size="small" />
</template>
AI
</Tag>
<Tag colorful shape="circle" type="solid">
<template #prefixIcon>
<IconAIFilledLevel1 size="small" />
</template>
AI
</Tag>
<Tag colorful shape="circle" type="light">
<template #prefixIcon>
<IconAIFilledLevel1 size="small" />
</template>
AI
</Tag>
<Tag colorful shape="circle" type="ghost">
<template #prefixIcon>
<IconAIFilledLevel1 size="small" />
</template>
AI
</Tag>
</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', rowGap: '10px' }">
<TypographyTitle :heading="5" :style="{ marginTop: '10px' }">AI FloatButton</TypographyTitle>
<div
:style="{
display: 'grid',
gridTemplateColumns: 'repeat(3,1fr)',
gridGap: '10px',
width: 'fit-content',
}"
>
<FloatButton aria-label="AI edit, VIP" :badge="{ count: 'VIP', type: 'danger' }" colorful size="large" :style="{ position: 'static' }">
<template #icon>
<IconAIEditLevel1 />
</template>
</FloatButton>
</div>
</div>
</div>
</template>AI Chat 组件构建对话
本例中我们将联合使用 AIChatInput 和 AIChatDialogue 组件,构建一个适用于 Multiple Agent 场景的对话用例。更多示例及使用场景详见 AIChatInput、AIChatDialogue。前后端组合使用用例可参考semi-ai-chat-demo
代码
<script setup lang="ts">
import { ref, shallowRef, type Component } from 'vue';
import { AIChatDialogue, AIChatInput, chatInputToMessage, type AIChatDialogueAnnotation as Annotation, type AIChatDialogueMessage as Message, type AIChatInputMessageContent as MessageContent, type AIChatInputReference as Reference, type AnnotationItemProps } from '@fibrous/semi-ai-vue';
import { IconBookOpenStroked, IconClose, IconFeishuLogo, IconFigma, IconGit, IconSearch, IconTemplateStroked, IconWord } from '@fibrous/semi-icons-vue';
import { Button, TypographyParagraph } from '@fibrous/semi-ui-vue';
const { Configure } = AIChatInput;
type SideBarContent = { type?: undefined; value?: undefined } | { type: 'annotation'; value: AnnotationItemProps[] } | { type: 'resource'; value: { name: string } };
const defaultMessages: Message[] = [
{
id: '1',
role: 'user',
content: '我想开发一个 Multi Agent 场景下的聊天应用,你能帮我设计一下吗?',
status: 'completed',
},
{
id: '2',
role: 'assistant',
name: 'PM',
content: [
{
type: 'message',
content: [
{
type: 'input_text',
text: '收到。为保证方案可落地,我先明确目标与范围:\n\n- 目标:支持多 Agent 协同回复,用户可选择 Agent 或由系统自动分配\n- MVP 功能:\n 1) 基础对话(文本/图片/文件)\n 2) Agent 身份标识与头像\n 3) 正在输入与流式输出\n 4) 引用来源与工具结果展示\n- 约束:先做单会话,不做云端持久化;优先移动端适配\n\n接下来我会整理 PRD 要点并同步给设计与前端。',
annotations: [
{
title: 'Semi Design',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户',
logo: '/assets/placeholder/logo.svg',
},
{
title: 'Semi DSM',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi DSM 支持全局、组件级别的样式定制,并在 Figma 和线上代码之间保持同步。使用 DSM,将 Semi Design 适配为 Any Design',
logo: '/assets/placeholder/logo.svg',
},
{
title: 'Semi D2C',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi D2C 提供开箱即用的设计稿转代码:支持一键识别 Figma 页面中图层布局 + 设计系统组件,像素级还原设计稿,转译为 React JSX 和 CSS 代码。此外还提供了丰富的扩展能力,基于自定义插件系统快速打造团队专属的设计研发协作工具。',
logo: '/assets/placeholder/logo.svg',
},
],
},
],
},
],
},
{
id: '3',
role: 'assistant',
name: 'PM',
content: [
{
type: 'message',
content: [
{
type: 'input_text',
text: '生成的PRD如下,设计师会先根据此摘要出信息架构与关键页面',
},
{
type: 'resource',
name: 'PRD.doc',
size: '100KB',
},
],
},
],
},
{
id: '4',
role: 'assistant',
name: 'UI',
content: [
{
id: 'rs_02175871288540800000000000000000000ffffac1598778c9aa5',
type: 'reasoning',
summary: [
{
type: 'summary_text',
text: '\n根据产品经理给的 PRD 绘制关键页面,我需要....',
},
],
status: 'completed',
},
{
type: 'function_call',
name: 'paint_key_pages',
arguments: '{"file":"PRD"}',
status: 'completed',
},
{
type: 'message',
content: [
{
type: 'output_text',
text: `设计初稿如下:\n\n- 信息架构:对话页(历史列表 | 消息流 | 工具卡片区)\n- 视觉:左侧展示 Agent 头像与名称标签,色块区分角色\n- 交互:\n - 输入区支持 @Agent 快速切换与建议提示\n - 流式输出时展示打字气泡与进度占位\n - 工具结果以卡片/步骤条形式插入,可展开详情与复制\n\n我先出低保真线框,稍后补高保真与动效说明。`,
},
],
status: 'completed',
},
],
status: 'completed',
},
{
id: '5',
role: 'assistant',
name: 'FE',
content: `技术方案建议:\n\n- 技术栈:React + Semi UI,后端采用 WebSocket 或 SSE 支持流式响应\n- 数据模型:消息包含 id、role、name、content、status、references 等字段\n- 组件拆分:AIChatInput + AIChatDialogue;内容采用 Markdown 渲染,支持图片与文件点击\n- 性能:虚拟列表与滚动置底;长文本分块渲染;图片懒加载\n- 可观测性:埋点消息延迟、出错率、工具调用耗时\n\n若确认,我可先搭建页面骨架并接入 mock 数据进行联调。`,
},
];
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/ai/img/user.png',
},
assistant: new Map([
[
'PM',
{
name: '产品经理',
avatar: '/assets/ai/PM.png',
},
],
[
'UI',
{
name: '设计师',
avatar: '/assets/ai/UI.png',
},
],
[
'FE',
{
name: '前端开发',
avatar: '/assets/ai/FE.png',
},
],
]),
};
const uploadProps = {
action: 'https://api.semi.design/upload',
};
const defaultInput = '我是一名<input-slot placeholder="[职业]">程序员</input-slot>,帮我实现<input-slot placeholder="[需求描述]">Multi Agent 场景下的聊天应用</input-slot>需求';
const modelOptions = [
{
value: 'GPT-5',
label: 'GPT-5',
type: 'gpt',
},
{
value: 'GPT-4o',
label: 'GPT-4o',
type: 'gpt',
},
{
value: 'Claude 3.5 Sonnet',
label: 'Claude 3.5 Sonnet',
type: 'claude',
},
];
const mcpOptions = [
{
label: '飞书文档',
value: 'feishu',
},
{
label: 'Github Mcp',
value: 'github',
},
{
label: 'IconFigma Mcp',
value: 'IconFigma',
},
];
const mcpIcons: Record<string, Component> = {
feishu: IconFeishuLogo,
github: IconGit,
IconFigma,
};
const radioOptions = [
{ label: '快速', value: 'fast' },
{ label: '思考', value: 'think' },
];
const inputOuterStyle = { margin: '12px', minHeight: '150px', maxHeight: '300px', flexShrink: 0 };
const editingInputOuterStyle = { margin: '12px 0px', maxHeight: '300px', flexShrink: 0 };
const dialogueOuterStyle = { flex: 1, overflow: 'auto' };
const sideBarTitleStyle = { fontSize: '16px', lineHeight: '22px', fontWeight: 600 };
const resourceButtonStyle = {
display: 'flex',
gap: '8px',
width: '100%',
backgroundColor: 'var(--semi-color-fill-0)',
border: 'none',
padding: '12px 16px',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '12px',
color: 'inherit',
font: 'inherit',
cursor: 'pointer',
};
const sideBarVisible = ref(false);
const messages = shallowRef(defaultMessages);
const generating = ref(false);
const references = shallowRef<Reference[]>([]);
const sideBarContent = shallowRef<SideBarContent>({});
const onReferenceClick = (item: Partial<Reference>) => {
references.value = [...references.value, { ...item, id: `reference-${Date.now()}` }];
};
const onReferenceDelete = (item: Reference) => {
references.value = references.value.filter((ref) => ref.id !== item.id);
};
const onMessageSend = (content: MessageContent) => {
generating.value = true;
messages.value = [...messages.value, { id: `message-${Date.now()}`, ...chatInputToMessage(content) }];
references.value = [];
setTimeout(() => {
generating.value = false;
}, 100);
setTimeout(() => {
messages.value = [
...messages.value,
{
id: `message-${Date.now()}`,
role: 'assistant',
name: 'FE',
content: '这是一条 mock 回复信息',
},
];
}, 1000);
};
const onEditMessageSend = (content: MessageContent) => {
const index = messages.value.findIndex((message) => message.editing);
messages.value = [...messages.value.slice(0, index), { id: `message-${Date.now()}`, ...chatInputToMessage(content) }];
};
const onEditingReferenceDelete = (item: Reference) => {
messages.value = messages.value.map((message) => (message.editing ? { ...message, references: message.references?.filter((ref) => ref.id !== item.id) } : message));
};
const changeSideBarContent = (content: SideBarContent) => {
sideBarVisible.value = JSON.stringify(content) === JSON.stringify(sideBarContent.value) ? !sideBarVisible.value : true;
sideBarContent.value = content;
};
const onAnnotationClick = (annotations: Annotation[]) => {
changeSideBarContent({ type: 'annotation', value: annotations });
};
const resourceOf = (item: unknown) => item as { name: string };
</script>
<template>
<div :style="{ display: 'flex', columnGap: '10px' }">
<div
:style="{
display: 'flex',
flexDirection: 'column',
height: 'calc(100vh - 32px)',
overflow: 'hidden',
flexGrow: 1,
}"
>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig" showReference :style="dialogueOuterStyle" @annotationClick="onAnnotationClick" @referenceClick="onReferenceClick">
<template #messageEdit="content">
<AIChatInput :defaultContent="content.inputContents?.[0].text" :generating="false" :references="content.references" :style="editingInputOuterStyle" :uploadProps="{ ...uploadProps, defaultFileList: content.attachments }" @messageSend="onEditMessageSend" @referenceDelete="onEditingReferenceDelete">
<template #configureArea>
<Configure.Select field="model" initValue="GPT-4o" :optionList="modelOptions" />
<Configure.Button field="onlineSearch">
<template #icon>
<IconBookOpenStroked />
</template>
联网搜索
</Configure.Button>
<Configure.Mcp :options="mcpOptions">
<template #optionIcon="{ item }">
<component :is="mcpIcons[item.value]" />
</template>
</Configure.Mcp>
<Configure.RadioButton field="thinkType" initValue="think" :options="radioOptions">
<template #label="{ option }">
<IconTemplateStroked v-if="option.value === 'fast'" :aria-label="option.label" />
<IconSearch v-else :aria-label="option.label" />
</template>
</Configure.RadioButton>
</template>
</AIChatInput>
</template>
<template #contentItem="{ item, type }">
<button v-if="type === 'resource'" :style="resourceButtonStyle" type="button" @click="changeSideBarContent({ type: 'resource', value: resourceOf(item) })">
<IconWord :style="{ color: 'var(--semi-color-primary)' }" />
{{ resourceOf(item).name }}
</button>
</template>
</AIChatDialogue>
<AIChatInput :defaultContent="defaultInput" :generating="generating" placeholder="输入内容或者上传内容" :references="references" :style="inputOuterStyle" :uploadProps="uploadProps" @messageSend="onMessageSend" @referenceDelete="onReferenceDelete" @stopGenerate="generating = false">
<template #configureArea>
<Configure.Select field="model" initValue="GPT-4o" :optionList="modelOptions" />
<Configure.Button field="onlineSearch">
<template #icon>
<IconBookOpenStroked />
</template>
联网搜索
</Configure.Button>
<Configure.Mcp :options="mcpOptions">
<template #optionIcon="{ item }">
<component :is="mcpIcons[item.value]" />
</template>
</Configure.Mcp>
<Configure.RadioButton field="thinkType" initValue="think" :options="radioOptions">
<template #label="{ option }">
<IconTemplateStroked v-if="option.value === 'fast'" :aria-label="option.label" />
<IconSearch v-else :aria-label="option.label" />
</template>
</Configure.RadioButton>
</template>
</AIChatInput>
</div>
<div
v-if="sideBarVisible"
:style="{
width: '300px',
height: 'calc(100vh - 32px)',
borderRadius: '12px',
border: '1px solid var(--semi-color-border)',
flexShrink: 0,
}"
>
<div
:style="{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px',
color: 'var(--semi-color-text)',
}"
>
<div v-if="sideBarContent.type === 'annotation'" :style="sideBarTitleStyle">参考资料</div>
<div v-if="sideBarContent.type === 'resource'" :style="sideBarTitleStyle">产物列表</div>
<Button aria-label="关闭" :style="{ padding: '0px', width: '24px', height: '24px' }" theme="borderless" type="tertiary" @click="sideBarVisible = !sideBarVisible">
<template #icon>
<IconClose />
</template>
</Button>
</div>
<div v-if="sideBarContent.type === 'annotation'" :style="{ display: 'flex', flexDirection: 'column', rowGap: '12px', padding: '12px' }">
<div v-for="(item, index) in sideBarContent.value" :key="index" :style="{ display: 'flex', flexDirection: 'column', rowGap: '8px' }">
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '4px' }">
<img alt="" :src="item.logo" :style="{ width: '20px', height: '20px', borderRadius: '50%' }" />
<span
:style="{
fontSize: '14px',
lineHeight: '20px',
fontWeight: 600,
color: 'var(--semi-color-text-0)',
}"
>
{{ item.title }}
</span>
</span>
<TypographyParagraph :ellipsis="{ rows: 3 }" :style="{ fontSize: '12px', lineHeight: '16px', color: 'var(--semi-color-text-1)' }">
{{ item.detail }}
</TypographyParagraph>
</div>
</div>
<div v-else-if="sideBarContent.type === 'resource'" :style="{ display: 'flex', flexDirection: 'column', rowGap: '12px', padding: '12px' }">
<div :style="{ display: 'flex', gap: '12px', alignItems: 'center' }">
<IconWord size="extra-large" :style="{ color: 'var(--semi-color-primary)' }" />
{{ sideBarContent.value.name }}
</div>
</div>
</div>
</div>
</template>FAQ
- Chat 组件和 AI Chat 系列组件应该如何选型?
- 如果场景比较简单,仅需要普通文字对话和简单文件图片展示,推荐 Chat 组件。
- Chat 组件默认集成 input 输入和消息展示部分,优势是理解简单,能够快速上手;缺点是复杂的输入框或者消息展示需求定制相对困难,工作量大。
- 如果场景相对复杂,推荐将 AIChatInput 和 AIChatDialogue 组件搭配使用,优势如下:
- AIChatInput 支持更复杂的样式定制。
- AIChatDialogue 消息展示的灵活性更高。
- 组件默认支持 OpenAI 的 Response / Chat Completion Object 格式标准,调用内部消息转换函数可轻松进行将 OpenAI 返回的结果转换为组件需要的数据结构。
- 提供根据消息类型的定制展示的 API,方便快速实现消息展示。
- 默认支持消息引用和选择操作。
- 如果场景比较简单,仅需要普通文字对话和简单文件图片展示,推荐 Chat 组件。



