When to use
AIChatDialogue can be used together with AIChatInput to build richer, more comprehensive and easier-to-customize AI conversation experiences. The component message format is based on OpenAI's Response Object, and supports the OpenAI community's Response / Chat Completion format standards by default. Responses to GPT-5 and GPT-4o series models are supported out of the box. For details, see Message Data Adapters.
Demos
How to import
<script setup lang="ts">
import { AIChatDialogue } from '@fibrous/semi-ai-vue';
</script>
Basic Usage
Bind v-model:chats to enable basic conversation display and interactions: when the message list changes, the component emits update:chats (and chatsChange, with the same payload). The component also detects in-place changes to chats: pushing a new message onto a reactive array, or appending text to the last message, emits chatsChange and follows the scroll. Changing only text deep inside a content array, or flipping status on its own, re-renders but neither emits chatsChange nor follows the scroll; replace that message or the whole array instead.
Use align to control layout: leftRight (default) and leftAlign.
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
jsx
Code
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-vue';
import { Radio, RadioGroup, type RadioChangeEvent } from '@fibrous/semi-ui-vue';
type Mode = NonNullable<AIChatDialogueProps['mode']>;
type Align = NonNullable<AIChatDialogueProps['align']>;
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef<Message[]>([
{
role: 'system',
id: '1',
createdAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createdAt: 1715676751919,
content: '给一个 Semi Design 的 Button 组件的使用示例',
},
{
role: 'assistant',
id: '3',
createdAt: 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<Mode>('bubble');
const align = ref<Align>('leftRight');
const onModeChange = (e: RadioChangeEvent) => {
mode.value = e.target.value as Mode;
};
const onAlignChange = (e: RadioChangeEvent) => {
align.value = e.target.value as Align;
};
</script>
<template>
<span :style="{ display: 'flex', flexDirection: 'column', rowGap: '8px' }">
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
模式
<RadioGroup aria-label="模式" :modelValue="mode" type="button" @change="onModeChange">
<Radio value="bubble">气泡</Radio>
<Radio value="noBubble">非气泡</Radio>
<Radio value="userBubble">用户会话气泡</Radio>
</RadioGroup>
</span>
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
会话布局方式
<RadioGroup aria-label="会话布局方式" :modelValue="align" type="button" @change="onAlignChange">
<Radio value="leftRight">左右分布</Radio>
<Radio value="leftAlign">左对齐</Radio>
</RadioGroup>
</span>
</span>
<div
:style="{
border: '1px solid var(--semi-color-border)',
borderRadius: '12px',
marginTop: '10px',
padding: '20px',
}"
>
<AIChatDialogue :key="align + mode" v-model:chats="messages" :align="align" :mode="mode" :roleConfig="roleConfig" />
</div>
</template>Auto Scroll
When the dialogue has a fixed height, new messages and streaming content keep the list at the bottom. Any upward scroll (wheel, scrollbar drag, keyboard) stops following; scrolling back to the bottom or sending a new message resumes it. Inserting earlier messages at the head of chats (such as loading history when scrolled to the top) does not scroll the list: the messages on screen stay in place.
点击下方按钮发送消息,回复会以流式方式逐步生成。
Code
<script setup lang="ts">
import { onBeforeUnmount, ref, shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { Button } from '@fibrous/semi-ui-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
};
const REPLY = '这是一段模拟的流式回复,每隔一小段时间追加几个字。回复生成期间,向上滚动(滚轮、拖动滚动条、PageUp 或方向键)会停止自动跟随,停在你滚到的位置;滚回底部后恢复跟随;发送新消息时也会回到底部。'.repeat(3);
const messages = shallowRef<Message[]>([{ role: 'assistant', id: 'welcome', content: '点击下方按钮发送消息,回复会以流式方式逐步生成。' }]);
const streaming = ref(false);
let timer: ReturnType<typeof setInterval> | undefined;
onBeforeUnmount(() => clearInterval(timer));
const send = () => {
const replyId = `reply-${Date.now()}`;
messages.value = [...messages.value, { role: 'user', id: `user-${Date.now()}`, content: '请生成一段较长的回复' }, { role: 'assistant', id: replyId, content: '', status: 'in_progress' }];
streaming.value = true;
let length = 0;
timer = setInterval(() => {
length += 6;
const done = length >= REPLY.length;
messages.value = messages.value.map((message) => (message.id === replyId ? { ...message, content: REPLY.slice(0, length), status: done ? 'completed' : 'in_progress' } : message));
if (done) {
clearInterval(timer);
streaming.value = false;
}
}, 100);
};
</script>
<template>
<Button :disabled="streaming" :style="{ marginBottom: '12px' }" @click="send">发送消息</Button>
<div :style="{ height: '320px' }">
<AIChatDialogue v-model:chats="messages" :roleConfig="roleConfig" />
</div>
</template>Message Status
chats is of type Message[]. Each Message contains various fields such as role role, content content, status status, unique identifier id, and creation time createdAt. See Message for details. The status follows the same values as the Response API Status: 6 status values mapping to 3 official styles (success / in progress / failed).
请求成功
请求错误
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
const defaultMessages: Message[] = [
{
role: 'assistant',
id: '1',
createdAt: 1715676751919,
content: '请求成功',
},
{
id: 'loading',
role: 'assistant',
status: 'in_progress', // renders like queued
},
{
role: 'assistant',
id: 'error',
content: '请求错误',
status: 'failed', // renders like cancelled
},
];
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef(defaultMessages);
</script>
<template>
<AIChatDialogue v-model:chats="messages" :roleConfig="roleConfig" />
</template>Message Display
The message content uses ContentItem[]. It supports blocks such as text, file, image, code, reasoning, annotation, and tool call. AIChatDialogue.Step is provided for step-by-step displays (e.g., workflows or plans).
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { IconBriefStroked, IconCodeStroked, IconSearchStroked } from '@fibrous/semi-icons-vue';
// A type alias, not an interface: only an alias is assignable to the step action's index signature
type PlanStep = {
type?: string;
summary?: string;
description?: string;
};
interface PlanItem {
summary: string;
steps?: PlanStep[];
}
const defaultMessages: Message[] = [
{
role: 'assistant',
id: '1',
createdAt: 1715676751919,
content: '普通文本',
},
{
id: '2',
role: 'user',
content: [
{
type: 'message',
content: [
{
type: 'input_text',
text: '帮我生成类似的图片',
},
{
type: 'input_image',
image_url: '/assets/root-web-sites/edit-bag.jpeg',
file_id: 'demo-file-id',
},
{
type: 'input_text',
text: '以下是文件展示',
},
{
type: 'input_file',
file_url: 'https://www.semi.pdf',
filename: 'semi.pdf',
size: '100KB',
},
{
type: 'input_file',
file_url: 'https://www.semi.json',
filename: 'semi.json',
size: '100KB',
},
{
type: 'input_file',
file_url: 'https://www.semi.docx',
filename: 'semi.docx',
size: '100KB',
},
],
},
],
status: 'completed',
},
{
id: '3',
role: 'assistant',
content: [
{
type: 'reasoning',
status: 'completed',
summary: [
{
type: 'summary_text',
text: '\n我需要思考并回答用户关于什么是 Semi 组件库的问题...',
},
],
},
{
type: 'message',
content: [
{
type: 'output_text',
text: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。',
},
],
status: 'completed',
},
{
id: 'fc_12345xyz',
call_id: 'call_12345xyz',
type: 'function_call',
name: 'get_weather',
status: 'completed',
arguments: "{'location':'Paris, France'}",
},
{
type: 'message',
content: [
{
type: 'output_text',
text: '恭喜你,你已经掌握了 semi design 的所有知识!',
annotations: [
{
title: 'semi.design',
url: 'https://semi.design/',
detail: 'semi design page',
logo: '/assets/placeholder/logo.svg',
},
{
title: 'semi.design',
url: 'https://semi.design/',
detail: 'semi design page',
logo: '/assets/placeholder/logo.svg',
},
],
},
],
},
{
type: 'plan',
content: [
{
summary: '创建一份全面的北京旅游攻略,包含景点、住宿、交通、美食和实用旅行建议',
steps: [
{
summary: '搜索北京旅游景点介绍及门票信息',
description: '正在搜索: 北京旅游景点介绍及门票信息',
type: 'search',
},
{
summary: '读取指定文件的指定行内容',
description: '正在创建文档: 北京旅游攻略',
type: 'docs',
},
{
summary: '创建包含北京旅游攻略的文件',
description: '正在创建代码文件: beijing_travel_guide.html',
type: 'code',
},
],
},
{
summary: '总结北京旅游攻略的创建成果并呈现给用户',
steps: [],
},
],
},
],
status: 'completed',
},
];
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef(defaultMessages);
// plan is a user-defined content type; its icons come from the #actionIcon slot, so the steps carry data only
const toSteps = (item: unknown) =>
(item as { content: PlanItem[] }).content.map((plan) => ({
summary: plan.summary,
actions: plan.steps ?? [],
status: 'completed',
}));
</script>
<template>
<AIChatDialogue v-model:chats="messages" :roleConfig="roleConfig">
<template #contentItem="{ item, type }">
<AIChatDialogue.Step v-if="type === 'plan'" :steps="toSteps(item)">
<template #actionIcon="{ item: action }">
<IconSearchStroked v-if="action.type === 'search'" />
<IconBriefStroked v-else-if="action.type === 'docs'" />
<IconCodeStroked v-else-if="action.type === 'code'" />
</template>
</AIChatDialogue.Step>
</template>
</AIChatDialogue>
</template>References
Use references to define files or text cited by the current message. showReference controls whether a quotable style is shown for the current message. Listen to @referenceClick for clicks on the reference button.
当前消息为引用 demo 的示例
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
const defaultMessages: Message[] = [
{
id: '1',
role: 'user',
content: '当前消息为引用 demo 的示例',
references: [
{
id: '1',
type: 'text',
content: '测试文本,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字',
},
{
id: '2',
name: '飞书文档.docx',
},
{
id: '3',
name: 'Music.mp4',
},
{
id: '4',
name: 'Image.jpeg',
url: '/assets/placeholder/avatar-b.svg',
},
{
id: '5',
name: 'code.json',
},
],
},
];
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef(defaultMessages);
const onReferenceClick = () => {
console.log('You click the reference button!');
};
</script>
<template>
<AIChatDialogue v-model:chats="messages" :roleConfig="roleConfig" showReference @referenceClick="onReferenceClick" />
</template>Selection
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
jsx
Code
<script setup lang="ts">
import { onMounted, ref, useTemplateRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueHandle, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { Radio, RadioGroup } from '@fibrous/semi-ui-vue';
const messages: Message[] = [
{
role: 'system',
id: '1',
createdAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createdAt: 1715676751919,
content: '给一个 Semi Design 的 Button 组件的使用示例',
},
{
role: 'assistant',
id: '3',
createdAt: 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 roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
// A Radio value of true cannot use the bare-attribute shorthand: the prop accepts strings, so it would be ''.
const selectOptions = [
{ label: '开启', value: true },
{ label: '关闭', value: false },
];
const dialogue = useTemplateRef<AIChatDialogueHandle>('dialogue');
const align = ref<'leftRight' | 'leftAlign'>('leftRight');
const select = ref(true);
const selection = ref<'allSelect' | 'cancelSelect'>('allSelect');
onMounted(() => dialogue.value?.selectAll());
const onSelectionChange = (value: unknown) => {
if (value === 'allSelect') {
dialogue.value?.selectAll();
selection.value = value;
} else if (value === 'cancelSelect') {
dialogue.value?.deselectAll();
selection.value = value;
}
};
const onAlignChange = (value: unknown) => {
if (value === 'leftRight' || value === 'leftAlign') align.value = value;
};
const onSelectChange = (value: unknown) => {
if (typeof value === 'boolean') select.value = value;
};
const onSelect = (selectionId: string[]) => {
console.log('onSelect', selectionId);
};
</script>
<template>
<div>
<span :style="{ display: 'flex', flexDirection: 'column', rowGap: '8px' }">
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
会话布局方式
<RadioGroup :modelValue="align" type="button" @update:modelValue="onAlignChange">
<Radio value="leftRight">左右分布</Radio>
<Radio value="leftAlign">左对齐</Radio>
</RadioGroup>
</span>
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
是否开启选择
<RadioGroup :modelValue="select" type="button" @update:modelValue="onSelectChange">
<Radio v-for="option in selectOptions" :key="option.label" :value="option.value">
{{ option.label }}
</Radio>
</RadioGroup>
</span>
<span :style="{ display: 'flex', alignItems: 'center', columnGap: '10px' }">
选择方式
<RadioGroup :modelValue="selection" type="button" @update:modelValue="onSelectionChange">
<Radio value="allSelect">全选</Radio>
<Radio value="cancelSelect">取消全选</Radio>
</RadioGroup>
</span>
</span>
<div
:style="{
border: '1px solid var(--semi-color-border)',
borderRadius: '12px',
marginTop: '10px',
padding: '20px',
}"
>
<AIChatDialogue ref="dialogue" :align="align" :chats="messages" mode="bubble" :roleConfig="roleConfig" :selecting="select" @select="onSelect" />
</div>
</div>
</template>Hints
Use hints to set the hint area content. Clicking a hint populates it as the new user input and emits hintClick.
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef<Message[]>([
{
role: 'assistant',
id: '1',
createdAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
]);
const hints = shallowRef(['Semi 组件库有哪些常用组件?', '能否展示一个使用 Semi 组件库构建的页面示例?', 'Semi 组件库有官方文档吗?']);
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" :hints="hints" mode="bubble" :roleConfig="roleConfig" @hintClick="hints = []" />
</template>Custom Hint Rendering
Customize the hint area with the #hintBox slot. Its parameters are { content, index, onHintClick }; calling onHintClick is the same as clicking the hint.
interface AIChatDialogueHintBoxSlotProps {
content: string;
index: number;
onHintClick: () => void;
}
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { IconArrowRight } from '@fibrous/semi-icons-vue';
const hintStyle = {
border: '1px solid var(--semi-color-border)',
padding: '10px',
borderRadius: '10px',
backgroundColor: 'transparent',
color: 'var(--semi-color-text-1)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
font: 'inherit',
fontSize: '14px',
};
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef<Message[]>([
{
role: 'assistant',
id: '1',
createdAt: 1715676751919,
content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
},
]);
const hints = shallowRef(['Semi 组件库有哪些常用组件?', '能否展示一个使用 Semi 组件库构建的页面示例?', 'Semi 组件库有官方文档吗?']);
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" :hints="hints" mode="bubble" :roleConfig="roleConfig" @hintClick="hints = []">
<template #hintBox="{ content, onHintClick }">
<button :style="hintStyle" type="button" @click="onHintClick">
{{ content }}
<IconArrowRight :style="{ marginLeft: '10px' }" />
</button>
</template>
</AIChatDialogue>
</template>Custom Chat Box Rendering
Each part is customized with a named scoped slot. Providing a slot replaces that part, and every slot receives the default nodes, so you can recompose them:
| Slot | Description | Parameters |
|---|---|---|
#avatar | Custom avatar | { message, role, defaultAvatar } |
#title | Custom title | { message, role, defaultTitle } |
#content | Custom content area | { message, role, defaultContent, class } |
#action | Custom message action bar | { message, defaultActions, defaultActionsObj, class } |
#fullDialogue | Fully custom rendering of the whole dialogue | { message, role, defaultNodes, class } |
defaultActions is the array of buttons in the default action bar, and defaultActionsObj holds the same buttons by name. They match the buttons the default action bar shows; a key is missing when its button is not shown (e.g. an unfinished message). defaultNodes holds the four default nodes avatar, title, content and action. Default nodes are VNodes: render them with <component :is="..." />.
interface DefaultActionNodeObj {
copyNode?: VNode;
likeNode?: VNode;
dislikeNode?: VNode;
resetNode?: VNode;
shareNode?: VNode;
editNode?: VNode;
moreNode?: VNode;
}
interface FullDialogueNodes {
avatar: VNode;
title: VNode;
content: VNode;
action: VNode;
}
Hello, I'm your AI assistant.
给一个 Semi Design 的 Button 组件的使用示例
以下是一个 Semi 代码的使用示例:
jsx
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { Avatar } from '@fibrous/semi-ui-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef<Message[]>([
{
role: 'system',
id: '1',
createdAt: 1715676751919,
content: "Hello, I'm your AI assistant.",
},
{
role: 'user',
id: '2',
createdAt: 1715676751919,
content: '给一个 Semi Design 的 Button 组件的使用示例',
},
{
role: 'assistant',
id: '3',
createdAt: 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",
},
]);
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig">
<template #title="{ role }">
<div class="semi-ai-chat-dialogue-title">My-{{ role?.name }}</div>
</template>
<template #avatar="{ role }">
<Avatar alt="" shape="square" size="extra-small" :src="role?.avatar" />
</template>
<template #action="{ defaultActions, class: className }">
<div :class="className">
<component :is="defaultActions[0]" />
</div>
</template>
</AIChatDialogue>
</template>Custom Message Content Rendering
Use the #contentItem slot to render by message type. Every content item (including the input text, images and files inside a message item) passes through the slot first, with the parameters { item, type, message, index, parent }. type is the item's type, and a top-level item without a type counts as message; for an inner item, parent is the message item it belongs to. When the slot returns empty content, the built-in rendering takes over; non-empty content is wrapped in semi-ai-chat-dialogue-content-custom-renderer. A message whose content is plain text is not a content item: replace its Markdown rendering with the #messageText slot, whose parameters are { text, message }. AIChatDialogue.Reasoning and AIChatDialogue.Annotation can be reused inside the slots; see Static Properties below. Example:


Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, type AIChatDialogueAnnotation as Annotation, type AIChatDialogueContentItemSlotProps as ContentItemSlotProps, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-vue';
import { MarkdownRender, Toast } from '@fibrous/semi-ui-vue';
// The fields of the content items this demo renders; the slot types `item` as the union of every item type.
interface DemoItem {
name?: string;
arguments?: string;
text?: string;
status?: string;
summary?: { text?: string; type?: string }[];
annotations?: Annotation[];
}
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef<Message[]>([
{
id: '1',
role: 'user',
content: '你好',
},
{
id: '2',
role: 'assistant',
content: '你好呀,请问有什么可以帮助你的吗~',
status: 'completed',
},
{
id: '3',
role: 'user',
content: [
{
type: 'message',
role: 'user',
content: [
{
type: 'input_text',
text: '帮我生成类似的图片',
},
{
type: 'input_image',
image_url: '/assets/root-web-sites/edit-bag.jpeg',
file_id: 'demo-file-id',
},
{
type: 'input_image',
image_url: '/assets/root-web-sites/edit-bag.jpeg',
file_id: 'demo-file-id',
},
],
},
],
},
{
id: '4',
role: 'assistant',
content: [
{
type: 'reasoning',
summary: [
{
type: 'summary_text',
text: '\n用户问需要我帮助他生成类似图片,我需要先分析图片内容,然后生成类似的图片...',
},
],
annotations: [
{
title: 'semi.design',
url: 'https://semi.design/',
detail: 'semi design page',
logo: '/assets/placeholder/logo.svg',
},
{
title: 'semi.design',
url: 'https://semi.design/',
detail: 'semi design page',
logo: '/assets/placeholder/logo.svg',
},
],
status: 'completed',
},
{
type: 'function_call',
name: 'create_travel_guide',
arguments: '{\n"city": "北京"\n}',
status: 'completed',
},
],
status: 'completed',
},
]);
const userTextStyle = {
backgroundColor: 'var(--semi-color-fill-1)',
color: 'var(--semi-color-text-0)',
borderRadius: '25px',
padding: '6px 16px',
};
const assistantStyle = {
color: 'var(--semi-color-text-0)',
padding: '6px 16px',
};
const functionCallStyle = {
backgroundColor: 'var(--semi-color-fill-1)',
padding: '6px 16px',
borderRadius: '25px',
};
const fields = (item: ContentItemSlotProps['item']) => item as DemoItem;
const onAnnotationClick = (e: MouseEvent | KeyboardEvent) => {
e.stopPropagation();
Toast.success('Ready to open the sidebar!');
};
</script>
<template>
<AIChatDialogue v-model:chats="messages" :roleConfig="roleConfig">
<template #contentItem="{ item, type, message }">
<div v-if="type === 'function_call' && fields(item).name === 'create_travel_guide'" :style="functionCallStyle">Function Tool Call: {{ fields(item).name }} {{ fields(item).arguments }}</div>
<template v-else-if="type === 'input_text'">
<div v-if="message.role === 'user'" class="userTextStyle" :style="userTextStyle">
{{ fields(item).text }}
</div>
<div v-else :style="assistantStyle">{{ fields(item).text }}</div>
</template>
<AIChatDialogue.Reasoning v-else-if="type === 'reasoning'" :status="fields(item).status" :summary="fields(item).summary">
<template #content="{ summary, markdownRenderProps }">
<AIChatDialogue.Annotation :annotation="fields(item).annotations ?? []" description="参考资料" :maxCount="3" @click="onAnnotationClick" />
<div :style="{ marginTop: '8px' }">
<MarkdownRender format="md" v-bind="markdownRenderProps" :raw="summary?.[0]?.text ?? ''" />
</div>
</template>
</AIChatDialogue.Reasoning>
</template>
<template #messageText="{ text, message }">
<div v-if="message.role === 'user'" class="userTextStyle" :style="userTextStyle">{{ text }}</div>
<div v-else :style="assistantStyle">{{ text }}</div>
</template>
</AIChatDialogue>
</template>Message Data Adapters
The current component's dialogue messages are modeled after OpenAI's Response Object. To better support users in seamlessly integrating the Chat Completion API and Response API, we provide four Adapter transformation functions. Users can directly use these functions to transform the API's return results to obtain data that can be directly used for message display. Two Adapter functions are provided to process the data of the ChatInput component into an input Message format adapted to the Response API or the Input Message format in the Chat Completion API.
import { chatCompletionToMessage, streamingChatCompletionToMessage, responseToMessage, streamingResponseToMessage, chatInputToMessage, chatInputToChatCompletion } from '@fibrous/semi-ai-vue';
// Convert the data returned by the Chat Completion API into the Message format in Chat Dialogue
function chatCompletionToMessage(chatCompletion: ChatCompletion): Message[];
// Convert the Chat Completion API streaming data into Message format for Chat Dialogue.
function streamingChatCompletionToMessage(chatCompletionChunks: ChatCompletionChunk[], state?: StreamingChatState): { messages: Message[]; state?: StreamingChatState };
// Convert the data returned by the Response API into the Message format in Chat Dialogue
function responseToMessage(response: Response): Message;
// Convert the streaming data returned by the Response API into the Message format in Chat Dialogue
// ResponseChunk is the union of Response streaming events, discriminated by type; returns null for empty chunks
function streamingResponseToMessage(chunks: ResponseChunk[], prevState?: StreamingResponseState): { message: Message | null; nextState: StreamingResponseState | null } | null;
// Convert Chat Input data to the Message format in Chat Dialogue (same as the Response API Input Message format)
// The result has no id; add one before putting it into chats
function chatInputToMessage(inputContent: MessageContent): Omit<Message, 'id'>;
// Convert Chat Input data to Input Message format in Chat Completion API
function chatInputToChatCompletion(inputContent: MessageContent): ChatCompletionInput;
For example, when a user returns non-streaming data using the Chat Completion API interface, the chatCompletionToMessage function can be used to convert the Chat Completion Object into a Dialogue Message block format. Note that because the Chat Completion API allows control over the number of results generated for each input message via n, this function returns an array. (Note: If n > 1, the user needs to decide which data to add to the message for display.)
Hello! How can I assist you today?
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, chatCompletionToMessage } from '@fibrous/semi-ai-vue';
const CHAT_COMPLETION_DATA = {
id: 'chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT',
object: 'chat.completion',
created: 1741569952,
model: 'gpt-4.1-2025-04-14',
choices: [
{
index: 0,
message: {
role: 'assistant',
content: 'Hello! How can I assist you today?',
refusal: null,
annotations: [],
tool_calls: [
{
id: 'call_abc123',
type: 'function',
function: {
name: 'get_current_weather',
arguments: '{\n"location": "Boston, MA"\n}',
},
},
],
},
logprobs: null,
finish_reason: 'stop',
},
],
// ...
};
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const messages = shallowRef(chatCompletionToMessage(CHAT_COMPLETION_DATA));
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig" />
</template>For example, when a user returns streaming data using the Chat Completion API interface, the streamingChatCompletionToMessage function can be used to convert the Chat Completion Chunk Object List into a Dialogue Message format.
Code
<script setup lang="ts">
import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
import { AIChatDialogue, streamingChatCompletionToMessage, type AIChatDialogueMessage as Message, type StreamingChatState } from '@fibrous/semi-ai-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const STREAMING_CHAT_COMPLETION_DATA = [
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { role: 'assistant', content: '', refusal: null }, finish_reason: null }],
obfuscation: 'ahPqlzj6DD',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { content: '' }, finish_reason: null }],
obfuscation: 'i2PXRIwvc3D',
},
// index 0: output text delta
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { content: '我正在使用 ' }, finish_reason: null }],
obfuscation: '3sslO5QylW',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { content: 'streamingChatCompletionToMessage' }, finish_reason: null }],
obfuscation: '3sslO5QylW',
},
// index 1: tool call delta (function_call / tool_calls)
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011845,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [
{
index: 1,
delta: {
tool_calls: [{ index: 0, id: 'call_1', function: { name: 'searchWeather', arguments: '{"city":"北京"' } }],
},
finish_reason: null,
},
],
obfuscation: 'T1',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011846,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [
{
index: 1,
delta: { tool_calls: [{ index: 0, function: { arguments: ',"day":"today"}' } }] },
finish_reason: null,
},
],
obfuscation: 'T2',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011844,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { content: ' 转换 Chat Completion Chunks' }, finish_reason: null }],
obfuscation: 'X1',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011844,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: { content: ' 🥳' }, finish_reason: null }],
obfuscation: 'X2',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
obfuscation: 'n13SLf',
},
{
id: 'chatcmpl-COjljxurV5GKrRUsg1wd7mIyQCiiT',
object: 'chat.completion.chunk',
created: 1760011843,
model: 'o3-mini-2025-01-31',
service_tier: 'default',
system_fingerprint: 'fp_6c43dcef8c',
choices: [{ index: 1, delta: {}, finish_reason: 'stop' }],
obfuscation: 'jt9rDb',
},
];
const messages = shallowRef<Message[]>([]);
let timer: ReturnType<typeof setInterval> | undefined;
onMounted(() => {
const total = STREAMING_CHAT_COMPLETION_DATA.length;
let i = 1;
let state: StreamingChatState | undefined;
timer = setInterval(() => {
if (i > total) {
clearInterval(timer);
return;
}
const slice = STREAMING_CHAT_COMPLETION_DATA.slice(0, i);
const { messages: partialMessages, state: nextState } = streamingChatCompletionToMessage(slice, state);
state = nextState;
messages.value = [partialMessages[0]];
i += 1;
}, 100);
});
onBeforeUnmount(() => clearInterval(timer));
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig" />
</template>When a user returns non-streaming data using the Response API interface, the responseToMessage function can be used to convert the Response Object into a Dialogue Message block format.
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
Code
<script setup lang="ts">
import { shallowRef } from 'vue';
import { AIChatDialogue, responseToMessage } from '@fibrous/semi-ai-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const RESPONSE_DATA = {
id: 'resp_67ccd3a9da748190baa7f1570fe91ac604becb25c45c1d41',
object: 'response',
created_at: 1741476777,
status: 'completed',
error: null,
incomplete_details: null,
instructions: null,
max_output_tokens: null,
model: 'gpt-4o-2024-08-06',
output: [
{
id: 'rs_6876cf02e0bc8192b74af0fb64b715ff06fa2fcced15a5ac',
type: 'reasoning',
status: 'completed',
summary: [
{
type: 'summary_text',
text: '**用户询问什么是 Semi Design** 用户问 “Semi Design”需整合多源信息。首先发现抖音的 Semi Design 是设计系统,支持多平台且含 Design Token 和代码转换工具。印度 Semi Design 专注半导体培训,但用户可能更关注抖音案例。其他结果涉及半定制设计,但关联性较低。需确认是否有其他解释,但当前信息已覆盖主要维度。虽然继续推理可能提高完备性,但现阶段已足够支撑答案,可以开始输出给用户。',
},
],
},
{
type: 'message',
id: 'msg_67ccd3acc8d48190a77525dc6de64b4104becb25c45c1d41',
status: 'completed',
role: 'assistant',
content: [
{
type: 'output_text',
text: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统',
annotations: [
{
title: 'Semi Design',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi Design 快速开始',
logo: '/assets/docs-icon.png',
},
{
title: 'Semi Design',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi Design 快速开始',
logo: '/assets/docs-icon.png',
},
{
title: 'Semi Design',
url: 'https://semi.design/zh-CN/start/getting-started',
detail: 'Semi Design 快速开始',
logo: '/assets/docs-icon.png',
},
],
},
],
},
{
id: 'fc_12345xyz',
call_id: 'call_12345xyz',
type: 'function_call',
name: 'get_semi_page',
status: 'completed',
arguments: '{"pageName":"AIChatDialogue"}',
},
],
// ...
};
const messages = shallowRef([responseToMessage(RESPONSE_DATA)]);
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig" />
</template>When a user returns streaming data using the Response API interface, the streamingResponseToMessage function can be used to convert the Response Chunk Object List into a Dialogue Message format.
Code
<script setup lang="ts">
import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
import { AIChatDialogue, streamingResponseToMessage, type AIChatDialogueMessage as Message, type ResponseChunk, type StreamingResponseState } from '@fibrous/semi-ai-vue';
const roleConfig = {
user: {
name: 'User',
avatar: '/assets/docs-icon.png',
},
assistant: {
name: 'Assistant',
avatar: '/assets/placeholder/logo.svg',
},
system: {
name: 'System',
avatar: '/assets/placeholder/logo.svg',
},
};
const FIXED_SHUFFLED_INDICES = [
0,
1,
2,
3,
4,
6, // chunk 5 skipped
6, // chunk 6 arrives twice
7,
5, // chunk 5 arrives late
8,
9,
10,
11,
12,
13,
14,
15,
16,
];
const REASONING_CHUNKS: ResponseChunk[] = [
{
type: 'response.created',
sequence_number: 0,
response: {
id: 'resp_reason_001',
object: 'response',
created_at: 1760091777,
status: 'in_progress',
background: false,
error: null,
incomplete_details: null,
instructions: null,
max_output_tokens: null,
max_tool_calls: null,
model: 'o3-mini-2025-01-31',
output: [],
parallel_tool_calls: true,
previous_response_id: null,
prompt_cache_key: null,
reasoning: { effort: 'medium', summary: null },
safety_identifier: null,
service_tier: 'auto',
store: true,
temperature: 1.0,
text: { format: { type: 'text' }, verbosity: 'medium' },
tool_choice: 'auto',
tools: [],
top_logprobs: 0,
top_p: 1.0,
truncation: 'disabled',
usage: null,
user: null,
metadata: {},
},
},
{
type: 'response.in_progress',
sequence_number: 1,
response: {
id: 'resp_reason_001',
object: 'response',
created_at: 1760091777,
status: 'in_progress',
background: false,
error: null,
incomplete_details: null,
instructions: null,
max_output_tokens: null,
max_tool_calls: null,
model: 'o3-mini-2025-01-31',
output: [],
parallel_tool_calls: true,
previous_response_id: null,
prompt_cache_key: null,
reasoning: { effort: 'medium', summary: null },
safety_identifier: null,
service_tier: 'auto',
store: true,
temperature: 1.0,
text: { format: { type: 'text' }, verbosity: 'medium' },
tool_choice: 'auto',
tools: [],
top_logprobs: 0,
top_p: 1.0,
truncation: 'disabled',
usage: null,
user: null,
metadata: {},
},
},
// reasoning item (output index 0)
{
type: 'response.output_item.added',
sequence_number: 2,
output_index: 0,
item: { id: 'rs_reason_001', type: 'reasoning', summary: [] },
},
{
type: 'response.reasoning_summary_part.added',
sequence_number: 3,
output_index: 0,
summary_index: 0,
part: { type: 'reasoning', text: '' },
},
{
type: 'response.reasoning_summary_text.delta',
sequence_number: 4,
output_index: 0,
summary_index: 0,
delta: '思',
},
{
type: 'response.reasoning_summary_text.delta',
sequence_number: 5,
output_index: 0,
summary_index: 0,
delta: '考',
},
{
type: 'response.reasoning_summary_text.delta',
sequence_number: 6,
output_index: 0,
summary_index: 0,
delta: '完',
},
{
type: 'response.reasoning_summary_text.delta',
sequence_number: 7,
output_index: 0,
summary_index: 0,
delta: '成',
},
{
type: 'response.reasoning_summary_text.delta',
sequence_number: 8,
output_index: 0,
summary_index: 0,
delta: '!',
},
{
type: 'response.reasoning_summary_text.done',
sequence_number: 9,
output_index: 0,
summary_index: 0,
text: '思考完成!',
},
{
type: 'response.output_item.done',
sequence_number: 10,
output_index: 0,
item: { id: 'rs_reason_001', type: 'reasoning', summary: [{ type: 'reasoning', text: '思考完成!' }] },
},
// assistant message (output index 1)
{
type: 'response.output_item.added',
sequence_number: 11,
output_index: 1,
item: { id: 'msg_reason_001', type: 'message', status: 'in_progress', content: [], role: 'assistant' },
},
{
type: 'response.content_part.added',
sequence_number: 12,
output_index: 1,
content_index: 0,
part: { type: 'output_text', annotations: [], text: '' },
},
{
type: 'response.output_text.delta',
sequence_number: 13,
output_index: 1,
content_index: 0,
delta: '基于上述思考,',
},
{
type: 'response.output_text.delta',
sequence_number: 14,
output_index: 1,
content_index: 0,
delta: '结论如下:',
},
{
type: 'response.output_text.done',
sequence_number: 15,
output_index: 1,
content_index: 0,
text: '基于上述思考,结论如下:...',
},
{
type: 'response.completed',
sequence_number: 16,
response: {
id: 'resp_reason_001',
object: 'response',
created_at: 1760091777,
status: 'completed',
background: false,
error: null,
incomplete_details: null,
instructions: null,
max_output_tokens: null,
max_tool_calls: null,
model: 'o3-mini-2025-01-31',
output: [
{ id: 'rs_reason_001', type: 'reasoning', summary: [{ type: 'reasoning', text: '思考完成!' }] },
{
id: 'msg_reason_001',
type: 'message',
status: 'completed',
content: [{ type: 'output_text', annotations: [], text: '基于上述思考,结论如下:...' }],
role: 'assistant',
},
],
parallel_tool_calls: true,
previous_response_id: null,
prompt_cache_key: null,
reasoning: { effort: 'medium', summary: null },
safety_identifier: null,
service_tier: 'default',
store: true,
temperature: 1.0,
text: { format: { type: 'text' }, verbosity: 'medium' },
tool_choice: 'auto',
tools: [],
top_logprobs: 0,
top_p: 1.0,
truncation: 'disabled',
usage: {
input_tokens: 12,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 120,
output_tokens_details: { reasoning_tokens: 16 },
total_tokens: 132,
},
user: null,
metadata: {},
},
},
];
const messages = shallowRef<Message[]>([]);
let timer: ReturnType<typeof setTimeout> | undefined;
onMounted(() => {
let currentLength = 0;
let currentState: StreamingResponseState | undefined;
const tick = () => {
timer = setTimeout(() => {
if (currentLength > 0) {
const currentIndices = FIXED_SHUFFLED_INDICES.slice(0, currentLength);
const currentChunks = currentIndices.map((index) => REASONING_CHUNKS[index]);
const result = streamingResponseToMessage(currentChunks, currentState);
if (result) {
const { message: responseMessage, nextState } = result;
if (responseMessage) {
messages.value = [responseMessage];
currentState = nextState;
}
}
}
currentLength += 1;
if (currentLength <= FIXED_SHUFFLED_INDICES.length) {
tick();
}
}, 200);
};
tick();
});
onBeforeUnmount(() => clearTimeout(timer));
</script>
<template>
<AIChatDialogue v-model:chats="messages" align="leftRight" mode="bubble" :roleConfig="roleConfig" />
</template>API Reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| align | Conversation layout: leftRight or leftAlign. | "leftRight" | "leftAlign" | semiDefault("AIChatDialogue", "align", "leftRight") |
| chats | Controlled list of conversation messages; bind it with v-model:chats. | Message[] | semiGlobalDefault("AIChatDialogue", "chats") |
| disabledFileItemClick | Whether clicking a file card is disabled. | boolean | semiDefault("AIChatDialogue", "disabledFileItemClick", false) |
| escapeHtml | Whether to escape HTML tags in user messages, so the Markdown parser does not treat them as HTML and drop content. | boolean | semiDefault("AIChatDialogue", "escapeHtml", true) |
| hintCls | Class name of the outermost hint area element. | string | semiGlobalDefault("AIChatDialogue", "hintCls") |
| hints | Hint texts. Clicking a hint turns it into a new user message and emits hintClick. | string[] | semiGlobalDefault("AIChatDialogue", "hints") |
| hintStyle | Style of the outermost hint area element. | StyleValue | semiGlobalDefault("AIChatDialogue", "hintStyle") |
| selecting | Whether multi-select mode is on. | boolean | semiDefault("AIChatDialogue", "selecting", false) |
| markdownRenderProps | Props passed to the MarkdownRender of each message. raw is decided by the message and cannot be passed; components are merged with the built-in ones, and a component with the same name (such as code) replaces the built-in one. | MarkdownRenderProps | semiGlobalDefault("AIChatDialogue", "markdownRenderProps") |
| mode | Conversation mode: bubble, noBubble or userBubble. | "bubble" | "noBubble" | "userBubble" | semiDefault("AIChatDialogue", "mode", "bubble") |
| roleConfig | Name, avatar and avatar color of each role (user, assistant, system and so on). Required, and not read from the global defaults. | RoleConfig | undefined |
| showReset | Whether to show the reset action. | boolean | semiDefault("AIChatDialogue", "showReset", true) |
| showReference | Whether to show the reference icon on text or file messages. Only user messages are affected. | boolean | semiDefault("AIChatDialogue", "showReference", false) |
Events
| Event | Description | Callback parameters |
|---|---|---|
| update:chats | Emitted when the message list changes; v-model:chats updates from it. It carries the same array as chatsChange. | [chats: Message[]] |
| chatsChange | Emitted when the message list changes, with the new message array. | [chats: Message[]] |
| annotationClick | Emitted when the references of a message block are clicked, with that block's annotations. | [annotations: Annotation[]] |
| fileClick | Emitted when a file card is clicked. | [file?: InputFile | undefined] |
| hintClick | Emitted when a hint is clicked. | [hint: string] |
| imageClick | Emitted when an image is clicked. | [image?: InputImage | undefined] |
| messageBadFeedback | Emitted when a message is disliked. | [message?: Message | undefined] |
| messageCopy | Emitted when a message is copied, after the text has been written to the clipboard successfully. The copied text excludes reasoning; a message without text is neither written nor reported. | [message?: Message | undefined] |
| messageDelete | Emitted when a message is deleted. | [message?: Message | undefined] |
| messageEdit | Emitted when a message is edited. | [message?: Message | undefined] |
| messageGoodFeedback | Emitted when a message is liked. | [message?: Message | undefined] |
| messageReset | Emitted when a message is reset. | [message?: Message | undefined] |
| messageShare | Emitted when a message is shared. | [message?: Message | undefined] |
| referenceClick | Emitted when a reference button is clicked. The payload has no id, which the caller adds before putting it into the references of AIChatInput; a file item has no type. | Type[item: Partial<Reference>] |
| select | Emitted when the selected messages change. | [selectedIds: string[]] |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| avatar | Custom avatar, with the default avatar defaultAvatar, the message and the role. | AIChatDialogueAvatarSlotProps |
| title | Custom title, with the default title defaultTitle, the message and the role. | AIChatDialogueTitleSlotProps |
| content | Custom content area, with the default content defaultContent, the message, the role and class. defaultContent matches the default rendering, including the reference list and the loading indicator. | AIChatDialogueContentSlotProps |
| action | Custom action bar, with the default buttons defaultActions, the same buttons by name defaultActionsObj, the message and class. A key is missing when its button is not shown, for example while the message is unfinished. | AIChatDialogueActionSlotProps |
| fullDialogue | Replaces a whole dialogue, with defaultNodes (avatar, title, content, action), the message, the role and class. | AIChatDialogueFullDialogueSlotProps |
| contentItem | Custom rendering by content item. Every item passes through it first, with item, type, message, index and parent; empty output leaves the item to the built-in rendering. | AIChatDialogueContentItemSlotProps |
| messageText | Replaces the Markdown rendering of a message whose content is plain text, with the text and the message. | AIChatDialogueMessageTextSlotProps |
| messageEdit | The editor shown while a user message is being edited; its parameters are the input content converted from the message. | MessageContent |
| hintBox | Custom hint item, with the hint text content, the index and the click callback onHintClick. | AIChatDialogueHintBoxSlotProps |
Exposes
| Name | Description | Type |
|---|---|---|
| selectAll | Selects all messages. | Type() => void |
| deselectAll | Deselects all messages. | Type() => void |
| scrollToBottom | Scrolls to the bottom, animated when the argument is true. | Type(animation: boolean) => void |
| scrollToTop | Scrolls to the top, animated when the argument is true. | Type(animation: boolean) => void |
class, style and native attributes fall through to the root element via attrs. When the root has aria-label or aria-labelledby and no role, its role is group; a role passed by the caller wins. The message list is a named role="log" region with aria-busy="true" while a reply is being generated. roleConfig is a required prop and does not read the global defaults.
RoleConfig
| Prop | Description | Type | Default |
|---|---|---|---|
| user | user info | Metadata | Map<string, Metadata> | - |
| assistant | assistant info | Metadata | Map<string, Metadata> | - |
| system | system info | Metadata | Map<string, Metadata> | - |
MetaData
| Prop | Description | Type | Default |
|---|---|---|---|
| name | Name | string | - |
| avatar | Avatar | string | - |
| color | Avatar background color, same as Avatar color prop. Supports amber, blue, cyan, green, grey, indigo, light-blue, light-green, lime, orange, pink, purple, red, teal, violet, yellow | AvatarColor | grey |
Message
| Prop | Description | Type | Default |
|---|---|---|---|
| role | Role | string | - |
| name | Name | string | - |
| id | Unique ID | string | - |
| content | Message content | string| ContentItem[] | - |
| createdAt | Created at (timestamp) | number | - |
| model | Model name | string | - |
| status | Message status: queued | in_progress | incomplete | completed | failed | cancelled | string | completed |
| editing | Whether the message is being edited, written by the component | boolean | - |
| like | Whether the message is liked, written by the component | boolean | - |
| dislike | Whether the message is disliked, written by the component | boolean | - |
| references | Files or text cited by the message | Reference[] | - |
| error | Streaming response error, written by streamingResponseToMessage | { code?: string; message?: string } | null | - |
Reference
The same type as AIChatInput's Reference: id and type are required, other fields are kept as they are.
| Prop | Description | Type | Default |
|---|---|---|---|
| id | Unique identifier, required | string | - |
| type | Type, required | string | - |
| name | Name | string | - |
| url | URL | string | - |
| content | Text content | string | - |
Methods
The methods are listed in the generated Exposes table above and are called through a template ref:
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { AIChatDialogue } from '@fibrous/semi-ai-vue';
const dialogue = useTemplateRef('dialogue');
const toBottom = () => dialogue.value?.scrollToBottom(true);
</script>
<template>
<AIChatDialogue ref="dialogue" v-model:chats="messages" :roleConfig="roleConfig" />
</template>
Static Properties
| Property | Description | Type |
|---|---|---|
| defaultComponents | Default Markdown rendering components. code is the enhanced code block component with a language label and copy button; it can be used to extend markdownRenderProps.components | { code: Component } |
| Reasoning | Reasoning block component; see AIChatDialogue.Reasoning below for its API | Component |
| Step | Step display component; see AIChatDialogue.Step below for its API | Component |
| Annotation | References component; see AIChatDialogue.Annotation below for its API | Component |
Usage Example:
To preserve default functionality while customizing code block rendering, wrap the default component in your own component and pass it through markdownRenderProps.components:
<!-- CustomCode.vue -->
<script setup lang="ts">
import { AIChatDialogue } from '@fibrous/semi-ai-vue';
defineOptions({ inheritAttrs: false });
const DefaultCode = AIChatDialogue.defaultComponents.code;
</script>
<template>
<!-- Add custom logic here -->
<DefaultCode v-bind="$attrs"><slot /></DefaultCode>
</template>
<script setup lang="ts">
import { AIChatDialogue } from '@fibrous/semi-ai-vue';
import CustomCode from './CustomCode.vue';
const markdownRenderProps = { components: { code: CustomCode } };
</script>
<template>
<AIChatDialogue v-model:chats="messages" :markdownRenderProps="markdownRenderProps" :roleConfig="roleConfig" />
</template>
AIChatDialogue.Reasoning
Props
| Property | Description | Type | Default |
|---|---|---|---|
| status | Reasoning status. completed shows the finished title and starts collapsed; any other value shows the thinking title and starts expanded. | string | undefined |
| summary | Reasoning summary. When it is not empty, its texts joined by line breaks form the reasoning text. | { text?: string | undefined; type?: string | undefined; }[] | undefined |
| content | Reasoning content. When summary is empty, its texts joined by line breaks form the reasoning text. | { text?: string | undefined; type?: string | undefined; }[] | undefined |
| markdownRenderProps | Props passed to the MarkdownRender of the reasoning text; raw comes from the reasoning text. | MarkdownRenderProps | undefined |
Events
| Event | Description | Callback parameters |
|---|---|---|
| None |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| content | Replaces the Markdown rendering of the reasoning text, with status, summary, content and markdownRenderProps. | ReasoningWidgetProps |
Exposes
| Name | Description | Type |
|---|---|---|
| None |
AIChatDialogue.Step
Props
| Property | Description | Type | Default |
|---|---|---|---|
| steps | List of steps. Each step has summary, status and actions; completed shows the done icon, any other status shows loading. actions hold data only (summary, description and so on), and a step expands or collapses on click, Enter or Space. | DialogueStep[] | undefined |
Events
| Event | Description | Callback parameters |
|---|---|---|
| None |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| actionIcon | Icon shown before each action's description, with the action item, its index, the step it belongs to and stepIndex. | DialogueStepWidgetActionIconSlotProps |
Exposes
| Name | Description | Type |
|---|---|---|
| None |
AIChatDialogue.Annotation
Props
| Property | Description | Type | Default |
|---|---|---|---|
| maxCount | Maximum number of source logos shown; the rest are shown as +N. | number | undefined |
| annotation | List of references; each item may have title, logo, url and detail. | AnnotationItemProps[] | Annotation[] | undefined |
| description | Text after the source logos; the number of references is shown when it is not set. | string | undefined |
Events
| Event | Description | Callback parameters |
|---|---|---|
| click | Emitted on click or on activation with Enter or Space, with the native event and the references. | [event: MouseEvent | KeyboardEvent, items: AnnotationItemProps[]] |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| None |
Exposes
| Name | Description | Type |
|---|---|---|
| None |
ContentItem
ContentItem supports all OpenAI Response InputItem and OutputItem types. Definitions:
type ContentItem = InputContentItem | OutputContentItem;
type InputContentItem = InputMessage | ItemReference;
type OutputContentItem = OutputMessage | MCPToolCall | Reasoning FileSearchToolCall |
WebSearchToolCall | FunctionToolCall | CustomToolCall | ImageGenerationCall| CustomObject;
interface CommonContentItem {
id?: string;
type?: string;
status?: string;
role?: string
}
interface CustomObject {
[key: string]: any
}
// input item
interface InputMessage extends CommonContentItem {
content?: string | (InputText | InputImage | InputFile)[]
}
interface InputText extends CommonContentItem {
text?: string
}
interface InputImage extends CommonContentItem {
detail?: string;
file_id?: string;
image_url?: string
}
interface InputFile extends CommonContentItem {
file_id?: string;
file_data?: string;
file_url?: string;
filename?: string;
size?: string;
file_type?: string;
fileInstance?: any
}
interface ItemReference extends CommonContentItem {
file_id?: string
}
// output item
interface OutputMessage extends CommonContentItem {
content?: (OutputText | Refusal)[]
}
interface OutputText {
text?: string;
type?: string;
annotations?: Annotation[]
}
interface Refusal extends CommonContentItem {
refusal?: string;
type?: string
}
type Annotation = URLCitation | CustomObject;
interface URLCitation {
end_index?: number;
start_index?: number;
title?: string;
type?: string;
url?: string
}
interface Reasoning extends CommonContentItem {
summary?: {
text?: string;
type?: string
}[];
content?: {
text?: string;
type?: string
}[]
}
interface FileSearchToolCallResult {
attributes?: Map<string, string>;
score?: number;
file_id?: string;
filename?: string;
text?: string
}
interface FileSearchToolCall extends CommonContentItem {
queries?: string[];
results?: FileSearchToolCallResult[]
}
interface WebSearchToolCall extends CommonContentItem {
action?: SearchAction | OpenPageAction | FindAction
}
interface SearchAction {
type?: string;
query?: string;
sources?: { type: string; url: string }[]
}
interface OpenPageAction {
type?: string;
url?: string
}
interface FindAction {
action?: string;
type?: string;
query?: string
}
interface FunctionToolCall extends CommonContentItem {
call_id?: string;
name?: string;
arguments?: string
}
interface CustomToolCall extends CommonContentItem {
call_id?: string;
name?: string;
input?: string
}
interface ImageGenerationCall extends CommonContentItem {
result?: string
}
interface MCPToolCall extends CommonContentItem {
arguments?: string;
server_label?: string;
name?: string;
result?: string;
output?: string
}
interface FileUploadToolCall extends CommonContentItem {
result?: string
}
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $color-aiChatDialogue_wrapper-selected-bg | var(--semi-color-fill-0) | 外层选中背景色 |
| $color-aiChatDialogue_loading_circle_first-bg | rgba(83, 56, 255, 1) | 第一个加载圆点背景色 |
| $color-aiChatDialogue_loading_circle_second-bg | rgba(176, 48, 240, 1) | 第二个加载圆点背景色 |
| $color-aiChatDialogue_loading_circle_third-bg | rgba(231, 45, 255, 1) | 第三个加载圆点背景色 |
| $color-aiChatDialogue_loading_text | var(--semi-color-text-3) | 加载中文本颜色 |
| $color-aiChatDialogue_bubble-bg | var(--semi-color-fill-0) | 对话气泡背景色 |
| $color-aiChatDialogue_file-bg | var(--semi-color-fill-0) | 文件组件背景色 |
| $color-aiChatDialogue_file_title-text | var(--semi-color-text-0) | 文件标题文本颜色 |
| $color-aiChatDialogue_file_metadata-text | var(--semi-color-text-2) | 文件元数据文本颜色 |
| $color-aiChatDialogue_tool_call-bg | var(--semi-color-fill-0) | 工具调用组件背景色 |
| $color-aiChatDialogue_tool_call-text | var(--semi-color-text-2) | 工具调用组件文本颜色 |
| $color-aiChatDialogue_action_dropdown-text | var(--semi-color-danger) | 操作区下拉菜单文本颜色 |
| $color-aiChatDialogue_reasoning-border | var(--semi-color-border) | 推理组件边框色 |
| $color-aiChatDialogue_step-border | var(--semi-color-border) | 步骤组件边框色 |
| $color-aiChatDialogue_step_action-bg | var(--semi-color-fill-0) | 步骤组件 action 背景色 |
| $color-aiChatDialogue_step_completed-text | var(--semi-color-success) | 步骤组件完成文本颜色 |
| $color-aiChatDialogue_step_suffix-text | var(--semi-color-text-1) | 步骤组件后缀文本颜色 |
| $color-aiChatDialogue_step_action-text | var(--semi-color-text-2) | 步骤组件 action 文本颜色 |
| $color-aiChatDialogue_annotation-border | var(--semi-color-border) | 参考来源组件边框色 |
| $color-aiChatDialogue_annotation-bg-hover | var(--semi-color-fill-0) | 参考来源组件悬停背景色 |
| $color-aiChatDialogue_annotation-text | var(--semi-color-text-2) | 参考来源组件文本颜色 |
| $color-aiChatDialogue_reference-bg | var(--semi-color-fill-0) | 引用组件背景色 |
| $color-aiChatDialogue_reference-text | var(--semi-color-text-2) | 引用组件文本颜色 |
| $color-aiChatDialogue_icon_reference-text | var(--semi-color-text-0) | 用户消息引用图标颜色 |
| $color-aiChatDialogue_code-border | var(--semi-color-border) | 代码块边框色 |
| $color-aiChatDialogue_code_topSlot-bg | var(--semi-color-fill-0) | 代码块顶部槽背景色 |
| $color-aiChatDialogue_code_topSlot-text | var(--semi-color-text-0) | 代码块顶部槽文本颜色 |
| $color-aiChatDialogue_hint-bg | var(--semi-color-bg-0) | 提示项背景色 |
| $color-aiChatDialogue_hint-border | var(--semi-color-border) | 提示项边框色 |
| $color-aiChatDialogue_hint-bg-hover | var(--semi-color-fill-0) | 提示项悬停背景色 |
| $color-aiChatDialogue_hint-text | var(--semi-color-text-0) | 提示项文本颜色 |
| $color-aiChatDialogue_code_icon-bg | var(--semi-teal-4) | code icon 背景色 |
| $color-aiChatDialogue_word_icon-bg | var(--semi-blue-4) | word icon 背景色 |
| $color-aiChatDialogue_excel_icon-bg | var(--semi-green-4) | excel icon 背景色 |
| $color-aiChatDialogue_pdf_icon-bg | var(--semi-red-4) | pdf icon 背景色 |
| $color-aiChatDialogue_video_icon-bg | var(--semi-purple-4) | video icon 背景色 |
| $color-aiChatDialogue_default_icon-bg | var(--semi-grey-4) | default icon 背景色 |
| $color-aiChatDialogue_icon-text | var(--semi-color-white) | icon 文本颜色 |
| $color-aiChatDialogue_failed | var(--semi-color-danger) | 失败提示颜色 |
| $color-aiChatDialogue_code_content-bg | var(--semi-color-bg-0) | code content 背景色 |
| $width-aiChatDialogue_wrapper | 100% | 外层宽度 |
| $width-aiChatDialogue_inner | 100% | 气泡右侧展示内层宽度 |
| $width-aiChatDialogue_backBottom_button | 42px | 返回按钮宽度 |
| $height-aiChatDialogue_backBottom_button | 42px | 返回按钮高度 |
| $width-aiChatDialogue_avatar | 24px | 头像宽度 |
| $height-aiChatDialogue_avatar | 24px | 头像高度 |
| $width-aiChatDialogue_content_editing | 100% | 内容宽度 - 编辑状态 |
| $width-aiChatDialogue_content_inner | 100% | 内容内层宽度 |
| $width-aiChatDialogue_content_max | 100% | 内容最大宽度 |
| $width-aiChatDialogue_bubble_max | 100% | 气泡最大宽度 |
| $width-aiChatDialogue_loading_circle | 3px | 加载圆点宽度 |
| $height-aiChatDialogue_loading_circle | 3px | 加载圆点高度 |
| $width-aiChatDialogue_img | 256px | 图片宽度 |
| $height-aiChatDialogue_img | 256px | 图片高度 |
| $width-aiChatDialogue_img_list | 128px | 图片列表项宽度 |
| $height-aiChatDialogue_img_list | 128px | 图片列表项高度 |
| $width-aiChatDialogue_file | 240px | 文件组件宽度(含内边距) |
| $width-aiChatDialogue_file_icon_wrapper | 36px | 文件图标外层宽度 |
| $height-aiChatDialogue_file_icon_wrapper | 36px | 文件图标外层高度 |
| $width-aiChatDialogue_action_btn | 24px | 操作按钮宽度 |
| $height-aiChatDialogue_action_btn | 24px | 操作按钮高度 |
| $width-aiChatDialogue_reasoning | 100% | 推理组件宽度 |
| $width-aiChatDialogue_reasoning_wrapper | 100% | 推理组件外层宽度 |
| $width-aiChatDialogue_reasoning-border | 1px | 推理组件 border 宽度 |
| $width-aiChatDialogue_reasoning_prefix | 20px | 推理前缀图标宽度 |
| $height-aiChatDialogue_reasoning_prefix | 20px | 推理前缀图标高度 |
| $width-aiChatDialogue_step-border | 1px | 步骤组件 border 宽度 |
| $width-aiChatDialogue_step_prefix | 20px | 步骤前缀图标宽度 |
| $height-aiChatDialogue_step_prefix | 20px | 步骤前缀图标高度 |
| $width-aiChatDialogue_step_suffix | 20px | 步骤后缀图标宽度 |
| $height-aiChatDialogue_step_suffix | 20px | 步骤后缀图标高度 |
| $width-aiChatDialogue_step_line | 1px | 步骤左侧线宽度 |
| $width-aiChatDialogue_annotation | 100% | 参考来源宽度 |
| $width-aiChatDialogue_annotation-border | 1px | 参考来源组件 border 宽度 |
| $width-aiChatDialogue_annotation_logo | 16px | 参考来源logo宽度 |
| $height-aiChatDialogue_annotation_logo | 16px | 参考来源logo高度 |
| $width-aiChatDialogue_reference_icon | 16px | 引用图标宽度 |
| $height-aiChatDialogue_reference_icon | 16px | 引用图标高度 |
| $width-aiChatDialogue_reference_max | 200px | 引用组件最大宽度 |
| $width-aiChatDialogue_reference_name_max | 130px | 引用名称最大宽度 |
| $width-aiChatDialogue_code-border | 1px | 代码块组件 border 宽度 |
| $width-aiChatDialogue_hint-border | 1px | 提示项组件 border 宽度 |
| $width-aiChatDialogue_file_title_max | 180px | 文件标题最大宽度 |
| $width-aiChatDialogue_file_title_max-hover | 140px | 用户消息悬停时文件标题最大宽度(显示引用图标时) |
| $width-aiChatDialogue_action_copy | 24px | 复制按钮宽度 |
| $spacing-aiChatDialogue_wrapper-paddingY | 8px | 最外层垂直内边距 |
| $spacing-aiChatDialogue_wrapper-paddingX | 16px | 最外层水平内边距 |
| $spacing-aiChatDialogue_wrapper-marginTop | 2px | 最外层顶部外边距 |
| $spacing-aiChatDialogue_wrapper-columnGap | 16px | 最外层列间距 |
| $spacing-aiChatDialogue_container-columnGap | 12px | container 容器列间距 |
| $spacing-aiChatDialogue_content_failed-marginRight | 8px | 内容失败提示右外边距 |
| $spacing-aiChatDialogue_content_bubble-marginTop | 8px | 内容气泡顶部外边距 |
| $spacing-aiChatDialogue_content_noBubble-marginTop | 8px | 内容无气泡顶部外边距 |
| $spacing-aiChatDialogue_bubble-paddingY | 12px | 气泡垂直内边距 |
| $spacing-aiChatDialogue_bubble-paddingX | 16px | 气泡水平内边距 |
| $spacing-aiChatDialogue_content_loading-marginTop | 8px | 内容加载顶部外边距 |
| $spacing-aiChatDialogue_loading_item-marginY | 1px | 加载项垂直外边距 |
| $spacing-aiChatDialogue_loading_item-marginX | 1px | 加载项水平外边距 |
| $spacing-aiChatDialogue_loading_text-marginLeft | 8px | 加载文本左外边距 |
| $spacing-aiChatDialogue_img-marginTop | 8px | 图片顶部外边距 |
| $spacing-aiChatDialogue_img_list-marginRight | 4px | 图片列表右外边距 |
| $spacing-aiChatDialogue_file-paddingY | 8px | 文件组件垂直内边距 |
| $spacing-aiChatDialogue_file-paddingX | 8px | 文件组件水平内边距 |
| $spacing-aiChatDialogue_file-columnGap | 8px | 文件组件列间距 |
| $spacing-aiChatDialogue_file-marginTop | 8px | 文件组件顶部外边距 |
| $spacing-aiChatDialogue_file-marginRight | 8px | 文件组件右外边距 |
| $spacing-aiChatDialogue_tool_call-paddingY | 6px | 工具调用垂直内边距 |
| $spacing-aiChatDialogue_tool_call-paddingX | 8px | 工具调用水平内边距 |
| $spacing-aiChatDialogue_tool_call-columnGap | 4px | 工具调用列间距 |
| $spacing-aiChatDialogue_tool_call-marginTop | 8px | 工具调用顶部外边距 |
| $spacing-aiChatDialogue_custom_renderer-marginTop | 8px | 自定义渲染器顶部外边距 |
| $spacing-aiChatDialogue_action-marginTop | 8px | 操作顶部外边距 |
| $spacing-aiChatDialogue_reasoning_wrapper-marginTop | 8px | 推理组件外层顶部外边距 |
| $spacing-aiChatDialogue_reasoning_header-padding | 16px | 推理头部内边距 |
| $spacing-aiChatDialogue_reasoning_content-paddingY | 12px | 推理内容垂直内边距 |
| $spacing-aiChatDialogue_reasoning_content-paddingX | 16px | 推理内容水平内边距 |
| $spacing-aiChatDialogue_step_wrapper-marginTop | 4px | 步骤外层顶部外边距 |
| $spacing-aiChatDialogue_step-paddingY | 6px | 步骤垂直内边距 |
| $spacing-aiChatDialogue_step-paddingX | 12px | 步骤水平内边距 |
| $spacing-aiChatDialogue_step-marginTop | 12px | 步骤顶部外边距 |
| $spacing-aiChatDialogue_step_prefix-marginRight | 6px | 步骤前缀右外边距 |
| $spacing-aiChatDialogue_step_suffix-marginLeft | 8px | 步骤后缀左外边距 |
| $spacing-aiChatDialogue_step_panel-gap | 12px | 步骤面板间距 |
| $spacing-aiChatDialogue_step_line-marginLeft | 20px | 步骤连接线左外边距 |
| $spacing-aiChatDialogue_step_action-marginTop | 12px | 步骤 action 顶部外边距 |
| $spacing-aiChatDialogue_step_action_summary-marginBottom | 4px | 步骤 action 摘要底部外边距 |
| $spacing-aiChatDialogue_step_action_desc-paddingY | 6px | 步骤 action 描述垂直内边距 |
| $spacing-aiChatDialogue_step_action_desc-paddingX | 8px | 步骤 action 描述水平内边距 |
| $spacing-aiChatDialogue_step_action_desc-columnGap | 4px | 步骤 action 描述列间距 |
| $spacing-aiChatDialogue_annotation_wrapper-marginTop | 8px | 参考来源外层顶部外边距 |
| $spacing-aiChatDialogue_annotation_wrapper-paddingY | 8px | 参考来源外层垂直内边距 |
| $spacing-aiChatDialogue_annotation_wrapper-paddingX | 12px | 参考来源外层水平内边距 |
| $spacing-aiChatDialogue_annotation_content-columnGap | 8px | 参考来源内容列间距 |
| $spacing-aiChatDialogue_references-marginTop | 8px | 引用顶部外边距 |
| $spacing-aiChatDialogue_references-columnGap | 4px | 引用列间距 |
| $spacing-aiChatDialogue_references-rowGap | 4px | 引用行间距 |
| $spacing-aiChatDialogue_reference-paddingY | 8px | 引用垂直内边距 |
| $spacing-aiChatDialogue_reference-paddingX | 12px | 引用水平内边距 |
| $spacing-aiChatDialogue_reference-columnGap | 8px | 引用列间距 |
| $spacing-aiChatDialogue_reference_icon-marginRight | 4px | 引用图标右外边距 |
| $spacing-aiChatDialogue_reference_img-marginRight | 4px | 引用图片右外边距 |
| $spacing-aiChatDialogue_icon_reference-paddingY | 0px | 用户消息引用图标垂直内边距 |
| $spacing-aiChatDialogue_icon_reference-paddingX | 8px | 用户消息引用图标水平内边距 |
| $spacing-aiChatDialogue_code_topSlot-paddingY | 8px | 代码块顶部槽垂直内边距 |
| $spacing-aiChatDialogue_code_topSlot-paddingX | 16px | 代码块顶部槽水平内边距 |
| $spacing-aiChatDialogue_hints-rowGap | 8px | 提示行间距 |
| $spacing-aiChatDialogue_hints-marginTop | 4px | 提示顶部外边距 |
| $spacing-aiChatDialogue_hints-marginBottom | 12px | 提示底部外边距 |
| $spacing-aiChatDialogue_hints-marginLeft | 52px | 提示左外边距 |
| $spacing-aiChatDialogue_hint_item-paddingY | 8px | 提示项垂直内边距 |
| $spacing-aiChatDialogue_hint_item-paddingX | 16px | 提示项水平内边距 |
| $spacing-aiChatDialogue_hints_selecting-marginLeft | 68px | 提示项选择状态下左外边距 |
| $radius-aiChatDialogue_backBottom_button | 50% | 返回按钮圆角 |
| $radius-aiChatDialogue_wrapper_selected | 16px | 外层选中圆角 |
| $radius-aiChatDialogue_loading_circle | var(--semi-border-radius-circle) | 加载圆点圆角 |
| $radius-aiChatDialogue_bubble | var(--semi-border-radius-large) | 对话气泡圆角 |
| $radius-aiChatDialogue_img | var(--semi-border-radius-medium) | 图片圆角 |
| $radius-aiChatDialogue_file | var(--semi-border-radius-medium) | 文件组件圆角 |
| $radius-aiChatDialogue_icon_wrapper | var(--semi-border-radius-extra-small) | 图标外层圆角 |
| $radius-aiChatDialogue_tool_call | var(--semi-border-radius-medium) | 工具调用组件圆角 |
| $radius-aiChatDialogue_reasoning_wrapper | var(--semi-border-radius-large) | 推理组件外层圆角 |
| $radius-aiChatDialogue_step | var(--semi-border-radius-medium) | 步骤组件圆角 |
| $radius-aiChatDialogue_action_desc | var(--semi-border-radius-medium) | 操作描述圆角 |
| $radius-aiChatDialogue_annotation_wrapper | var(--semi-border-radius-medium) | 参考来源外层圆角 |
| $radius-aiChatDialogue_reference | var(--semi-border-radius-medium) | 引用组件圆角 |
| $radius-aiChatDialogue_reference_icon | 2px | 引用图标圆角 |
| $radius-aiChatDialogue_code | var(--semi-border-radius-large) | 代码块圆角 |
| $radius-aiChatDialogue_hint_item | var(--semi-border-radius-medium) | 提示项圆角 |
| $font-aiChatDialogue_title-fontSize | $font-size-header-6 | 标题字体大小 |
| $font-aiChatDialogue_title-fontWeight | $font-weight-regular | 标题字体粗细 |
| $font-aiChatDialogue_title-lineHeight | 20px | 标题行高 |
| $font-aiChatDialogue_content-fontSize | $font-size-regular | 内容字体大小 |
| $font-aiChatDialogue_loading_text-fontSize | $font-size-regular | 加载文本字体大小 |
| $font-aiChatDialogue_file_title-fontSize | $font-size-header-6 | 文件标题字体大小 |
| $font-aiChatDialogue_file_metadata-fontSize | $font-size-regular | 文件元数据字体大小 |
| $font-aiChatDialogue_reasoning_header_title-fontWeight | $font-weight-bold | 推理头部标题字体粗细 |
| $font-aiChatDialogue_reasoning_header_title-lineHeight | 22px | 推理头部标题行高 |
| $font-aiChatDialogue_dropdown_icon-fontSize | $font-size-header-6 | 下拉菜单图标字体大小 |
| $font-aiChatDialogue_step-fontSize | $font-size-regular | 步骤字体大小 |
| $font-aiChatDialogue_step-lineHeight | 20px | 步骤行高 |
| $font-aiChatDialogue_step_action-fontSize | $font-size-regular | 步骤 action 字体大小 |
| $font-aiChatDialogue_step_action-lineHeight | 20px | 步骤 action 行高 |
| $font-aiChatDialogue_annotation_content-fontSize | $font-size-regular | 参考来源内容字体大小 |
| $font-aiChatDialogue_references-fontSize | 12px | 引用字体大小 |
| $font-aiChatDialogue_references-lineHeight | $font-size-header-6 | 引用行高 |
| $font-aiChatDialogue_code_topSlot-fontSize | $font-size-regular | 代码块顶部槽字体大小 |
| $font-aiChatDialogue_code-lineHeight | 32px | 代码块行高 |
| $font-aiChatDialogue_hint_content-fontSize | $font-size-regular | 提示内容字体大小 |
| $z-aiChatDialogue_backBottom | 1 | 返回按钮z-index |