AI 组件

AI对话

用户展示 AI 聊天中的对话信息

使用场景

AIChatDialogue 组件可搭配 AIChatInput 使用,实现更丰富的、功能覆盖更全面、定制更加便捷的 AI 会话场景。组件消息格式以 OpenAI 的 Response Object 为原型,默认支持 OpenAI 社区 Response / Chat Completion 格式标准,对 GPT-5、GPT-4o 系列模型的响应均支持开箱即用,详见消息数据转换。

代码演示

如何引入

import { AIChatDialogue } from '@fibrous/semi-ai-react';

基本用法

通过设置 chats 和 onChatsChange 实现基础对话显示和交互。

使用 align 属性可以设置对话的布局,支持左右分布(leftRight, 默认)和左对齐(leftAlign)。

模式
会话布局方式
System

Hello, I'm your AI assistant.

User

给一个 Semi Design 的 Button 组件的使用示例

Assistant

以下是一个 Semi 代码的使用示例:

jsx
import React from 'react';
import { Button } from '@fibrous/semi-ui-react';

const MyComponent = () => {
  return (
    <Button>Click me</Button>
 );
};
export default MyComponent;
代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-react';
import { RadioGroup, Radio, type RadioChangeEvent } from '@fibrous/semi-ui-react';

const defaultMessages: 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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(defaultMessages);
    const [mode, setMode] = useState<NonNullable<AIChatDialogueProps['mode']>>('bubble');
    const [align, setAlign] = useState<NonNullable<AIChatDialogueProps['align']>>('leftRight');

    const onAlignChange = useCallback((e: RadioChangeEvent) => {
        setAlign(e.target.value);
    }, []);

    const onModeChange = useCallback((e: RadioChangeEvent) => {
        setMode(e.target.value);
    }, []);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    return (
        <>
            <span style={{ display: 'flex', flexDirection: 'column', rowGap: '8px' }}>
                <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}>
                    模式
                    <RadioGroup aria-label="模式" 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 aria-label="会话布局方式" onChange={onAlignChange} value={align} type="button">
                        <Radio value="leftRight">左右分布</Radio>
                        <Radio value="leftAlign">左对齐</Radio>
                    </RadioGroup>
                </span>
            </span>
            <div style={{ border: '1px solid var(--semi-color-border)', borderRadius: 12, marginTop: 10, padding: 20 }}>
                <AIChatDialogue key={align + mode} align={align} mode={mode} chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />
            </div>
        </>
    );
}

自动滚动

对话区高度固定时,新消息和流式输出的内容会让列表保持在底部。任何向上的滚动(滚轮、拖动滚动条、键盘)都会停止跟随;滚回底部或发送新消息后恢复。在 chats 头部插入更早的消息(如滚到顶部时加载历史)不会滚动列表,已显示的消息保持在原位置。

Assistant

点击下方按钮发送消息,回复会以流式方式逐步生成。

代码
import { useEffect, useRef, useState } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';
import { Button } from '@fibrous/semi-ui-react';

const roleConfig = {
    user: {
        name: 'User',
        avatar: '/assets/docs-icon.png',
    },
    assistant: {
        name: 'Assistant',
        avatar: '/assets/placeholder/logo.svg',
    },
};

const REPLY = '这是一段模拟的流式回复,每隔一小段时间追加几个字。回复生成期间,向上滚动(滚轮、拖动滚动条、PageUp 或方向键)会停止自动跟随,停在你滚到的位置;滚回底部后恢复跟随;发送新消息时也会回到底部。'.repeat(3);

const defaultMessages: Message[] = [{ role: 'assistant', id: 'welcome', content: '点击下方按钮发送消息,回复会以流式方式逐步生成。' }];

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = useState(defaultMessages);
    const [streaming, setStreaming] = useState(false);
    const timerRef = useRef<ReturnType<typeof setInterval>>(undefined);
    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

    useEffect(() => () => clearInterval(timerRef.current), []);

    const send = () => {
        const replyId = `reply-${Date.now()}`;
        setMessages((prev) => [...prev, { role: 'user', id: `user-${Date.now()}`, content: '请生成一段较长的回复' }, { role: 'assistant', id: replyId, content: '', status: 'in_progress' }]);
        setStreaming(true);
        let length = 0;
        timerRef.current = setInterval(() => {
            length += 6;
            const done = length >= REPLY.length;
            setMessages((prev) => prev.map((message) => (message.id === replyId ? { ...message, content: REPLY.slice(0, length), status: done ? 'completed' : 'in_progress' } : message)));
            if (done) {
                clearInterval(timerRef.current);
                setStreaming(false);
            }
        }, 100);
    };

    return (
        <>
            <Button onClick={send} disabled={streaming} style={{ marginBottom: '12px' }}>
                发送消息
            </Button>
            <div style={{ height: '320px' }}>
                <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />
            </div>
        </>
    );
}

消息状态

chats 类型为 Message[], Message 包含对话的各种信息,如角色 role、内容 content、状态 status 、唯一标识 id、创建时间 createdAt 等,具体见 Message。其中 status 和 Response API Status 相同,存在 6 种状态,对应 3 种官方样式(成功 / 请求中 / 失败)。

Assistant

请求成功

Assistant
请稍候...
Assistant

请求错误

代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';

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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(defaultMessages);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    return <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />;
}

消息展示

消息内容展示的类型为 ContentItem[],支持文本 text、文件 file、图片 image、代码 code、思考块 reasoning、参考来源 annotation、工具调用 tool call 等消息块的展示,同时提供 AIChatDialogue.Step 组件用于步骤等信息的分步展示。

Assistant

普通文本

User

帮我生成类似的图片

图片

以下是文件展示

semi.pdf
semi.json
semi.docx
Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统。

get_weather {'location':'Paris, France'}
2 篇资料

恭喜你,你已经掌握了 semi design 的所有知识!

创建一份全面的北京旅游攻略,包含景点、住宿、交通、美食和实用旅行建议
搜索北京旅游景点介绍及门票信息
正在搜索: 北京旅游景点介绍及门票信息
读取指定文件的指定行内容
正在创建文档: 北京旅游攻略
创建包含北京旅游攻略的文件
正在创建代码文件: beijing_travel_guide.html
总结北京旅游攻略的创建成果并呈现给用户
代码
import { useState, useCallback, useMemo } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type DialogueContentItemRendererMap } from '@fibrous/semi-ai-react';
import { IconSearchStroked, IconCodeStroked, IconBriefStroked } from '@fibrous/semi-icons-react';

interface PlanStep {
    type?: string;
    summary?: string;
    description?: string;
}

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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(defaultMessages);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    const mapStep = useCallback((steps?: PlanStep[]) => {
        if (!steps) {
            return [];
        }
        return steps.map((item) => {
            let icon = null;
            switch (item.type) {
                case 'search':
                    icon = <IconSearchStroked />;
                    break;
                case 'docs':
                    icon = <IconBriefStroked />;
                    break;
                case 'code':
                    icon = <IconCodeStroked />;
                    break;
            }
            return {
                summary: item.summary,
                description: item.description,
                icon: icon,
            };
        });
    }, []);

    const customRender = useMemo<DialogueContentItemRendererMap>(
        () => ({
            plan: (item) => {
                let steps = item.content.map((item: { summary: string; steps?: PlanStep[] }) => {
                    return {
                        summary: item.summary,
                        actions: mapStep(item.steps),
                        status: 'completed',
                    };
                });
                return <AIChatDialogue.Step steps={steps} />;
            },
        }),
        [mapStep]
    );

    return <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} renderDialogueContentItem={customRender} />;
}

引用

通过 references 字段定义当前消息引用的文件或者文本, showReference 配置当前消息是否显示可被引用样式, onReferenceClick 配置引用按钮点击回调。具体和 AIChatInput 的搭配使用见 AI 组件构建对话

User
测试文本,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字,这里是一段很长的文字
飞书文档.docx
Music.mp4
Image.jpeg
code.json

当前消息为引用 demo 的示例

代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';

const defaultMessages = [
    {
        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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState<Message[]>(defaultMessages);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    const onReferenceClick = () => {
        console.log('You click the reference button!');
    };

    return <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} showReference onReferenceClick={onReferenceClick} />;
}

选择

会话布局方式
是否开启选择
选择方式
System

Hello, I'm your AI assistant.

User

给一个 Semi Design 的 Button 组件的使用示例

Assistant

以下是一个 Semi 代码的使用示例:

jsx
import React from 'react';
import { Button } from '@fibrous/semi-ui-react';

const MyComponent = () => {
  return (
    <Button>Click me</Button>
 );
};
export default MyComponent;
代码
import { useState, useCallback, useRef, useEffect } from 'react';
import { AIChatDialogue, type AIChatDialogueHandle, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-react';
import { RadioGroup, Radio, type RadioChangeEvent } from '@fibrous/semi-ui-react';

const defaultMessages: 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',
    },
};

export default function AIChatDialogueDemo() {
    const ref = useRef<AIChatDialogueHandle>(null);
    const [messages] = useState(defaultMessages);
    const [align, setAlign] = useState<AIChatDialogueProps['align']>('leftRight');
    const [select, setSelect] = useState(true);
    const [selection, setSelection] = useState('allSelect');

    useEffect(() => {
        ref.current?.selectAll();
    }, []);

    const onSelectChange = useCallback((e: RadioChangeEvent) => {
        setSelect(e.target.value);
    }, []);

    const onSelectionChange = useCallback((e: RadioChangeEvent) => {
        if (e.target.value === 'allSelect') {
            ref.current?.selectAll();
        } else {
            ref.current?.deselectAll();
        }
        setSelection(e.target.value);
    }, []);

    const onSelect = useCallback((selectionId: string[]) => {
        console.log('onSelect', selectionId);
    }, []);

    const onAlignChange = useCallback((e: RadioChangeEvent) => {
        setAlign(e.target.value);
    }, []);

    return (
        <div>
            <span style={{ display: 'flex', flexDirection: 'column', rowGap: '8px' }}>
                <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 style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}>
                    是否开启选择
                    <RadioGroup onChange={onSelectChange} value={select} type="button">
                        <Radio value>开启</Radio>
                        <Radio value={false}>关闭</Radio>
                    </RadioGroup>
                </span>
                <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}>
                    选择方式
                    <RadioGroup onChange={onSelectionChange} value={selection} type="button">
                        <Radio value="allSelect">全选</Radio>
                        <Radio value="cancelSelect">取消全选</Radio>
                    </RadioGroup>
                </span>
            </span>

            <div style={{ border: '1px solid var(--semi-color-border)', borderRadius: 12, marginTop: 10, padding: 20 }}>
                <AIChatDialogue ref={ref} align={align} mode="bubble" chats={messages} selecting={select} onSelect={onSelect} roleConfig={roleConfig} />
            </div>
        </div>
    );
}

提示

通过 hints 可设置提示区域内容, 点击提示内容后,提示内容将成为新的用户输入内容,并触发 onHintClick 回调。

Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。

Semi 组件库有哪些常用组件?
能否展示一个使用 Semi 组件库构建的页面示例?
Semi 组件库有官方文档吗?
代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';

export default function AIChatDialogueDemo() {
    const defaultMessages = [
        {
            role: 'assistant',
            id: '1',
            createdAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ];

    const hintsExample = ['Semi 组件库有哪些常用组件?', '能否展示一个使用 Semi 组件库构建的页面示例?', 'Semi 组件库有官方文档吗?'];

    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, setMessage] = useState<Message[]>(defaultMessages);
    const [hints, setHints] = useState(hintsExample);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    const onHintClick = useCallback(() => {
        setHints([]);
    }, []);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} hints={hints} onHintClick={onHintClick} />;
}

自定义渲染提示

通过 renderHintBox 可自定义提示区域内容, 参数如下

type renderHintBox = (props: { content: string; index: number; onHintClick: () => void }) => React.ReactNode;
Assistant

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。

代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-react';
import { IconArrowRight } from '@fibrous/semi-icons-react';

const commonHintStyle = {
    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',
};

type HintBoxProps = Parameters<NonNullable<AIChatDialogueProps['renderHintBox']>>[0];

export default function AIChatDialogueDemo() {
    const defaultMessages = [
        {
            role: 'assistant',
            id: '1',
            createdAt: 1715676751919,
            content: 'Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统,你可以向我提问任何关于 Semi 的问题。',
        },
    ];

    const hintsExample = ['Semi 组件库有哪些常用组件?', '能否展示一个使用 Semi 组件库构建的页面示例?', 'Semi 组件库有官方文档吗?'];

    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, setMessage] = useState<Message[]>(defaultMessages);
    const [hints, setHints] = useState(hintsExample);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    const onHintClick = useCallback(() => {
        setHints([]);
    }, []);

    const renderHintBox = useCallback(({ content, onHintClick }: HintBoxProps) => {
        return (
            <button type="button" style={commonHintStyle} onClick={onHintClick}>
                {content}
                <IconArrowRight style={{ marginLeft: 10 }} />
            </button>
        );
    }, []);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} hints={hints} onHintClick={onHintClick} renderHintBox={renderHintBox} />;
}

自定义渲染会话框

通过 dialogueRenderConfig 传入自定义渲染配置, dialogueRenderConfig 类型如下

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;
    shareNode?: ReactNode;
    editNode?: ReactNode;
    moreNode?: ReactNode;
}

export interface RenderActionProps {
    message?: Message;
    defaultActions?: ReactNode | ReactNode[];
    className: string;
    defaultActionsObj?: DefaultActionNodeObj;
}

export interface FullDialogueNodes {
    avatar?: ReactNode;
    title?: ReactNode;
    content?: ReactNode;
    action?: ReactNode;
}

export interface RenderFullDialogueProps {
    message?: Message;
    role?: Metadata;
    defaultNodes?: FullDialogueNodes;
    className: string;
}

export interface DialogueRenderConfig {
    /* 自定义渲染消息操作栏 */
    renderDialogueAction?: (props: RenderActionProps) => ReactNode;
    /* 自定义渲染头像 */
    renderDialogueAvatar?: (props: RenderAvatarProps) => ReactNode;
    /* 自定义渲染内容区域 */
    renderDialogueContent?: (props: RenderContentProps) => ReactNode;
    /* 自定义渲染标题 */
    renderDialogueTitle?: (props: RenderTitleProps) => ReactNode;
    /* 完全自定义渲染整个聊天框 */
    renderFullDialogue?: (props: RenderFullDialogueProps) => ReactNode;
}

自定义渲染头像和标题,可通过 renderDialogueAvatar 和 renderDialogueTitle 实现。

My-System

Hello, I'm your AI assistant.

My-User

给一个 Semi Design 的 Button 组件的使用示例

My-Assistant

以下是一个 Semi 代码的使用示例:

jsx
import React from 'react';
import { Button } from '@fibrous/semi-ui-react';

const MyComponent = () => {
  return (
    <Button>Click me</Button>
 );
};
export default MyComponent;
代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type DialogueRenderConfig } from '@fibrous/semi-ai-react';
import { Avatar } from '@fibrous/semi-ui-react';

export default function AIChatDialogueDemo() {
    const defaultMessages = [
        {
            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',
        },
    };

    const [messages, setMessage] = useState<Message[]>(defaultMessages);
    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    const renderConfig: DialogueRenderConfig = {
        renderDialogueTitle: (props) => {
            return <div className="semi-ai-chat-dialogue-title">My-{props.role?.name}</div>;
        },
        renderDialogueAvatar: (props) => {
            return <Avatar src={props.role?.avatar} alt="" size="extra-small" shape="square" />;
        },
        renderDialogueAction: (props) => {
            return <div className={props.className}>{Array.isArray(props.defaultActions) && props.defaultActions[0]}</div>;
        },
    };

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} dialogueRenderConfig={renderConfig} />;
}

自定义渲染消息内容

通过 renderDialogueContentItem 按照消息类型返回内容渲染,用法如下

User
你好
Assistant
你好呀,请问有什么可以帮助你的吗~
User
帮我生成类似的图片
图片
图片
Assistant
Function Tool Call: create_travel_guide { "city": "北京" }
代码
import React, { useState, useCallback } from 'react';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type AIChatDialogueAnnotation as Annotation, type DialogueContentItemRendererMap, type ReasoningWidgetProps } from '@fibrous/semi-ai-react';
import { MarkdownRender, Toast } from '@fibrous/semi-ui-react';

const defaultMessages: 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 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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(defaultMessages);
    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    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 customRenderReasoningContent = useCallback((props: ReasoningWidgetProps) => {
        // the reasoning item also carries its annotations, which the widget props do not declare
        const { annotations = [] } = props as ReasoningWidgetProps & { annotations?: Annotation[] };
        return (
            <React.Fragment>
                <AIChatDialogue.Annotation
                    annotation={annotations}
                    description="参考资料"
                    maxCount={3}
                    onClick={(e) => {
                        e?.stopPropagation();
                        Toast.success('Ready to open the sidebar!');
                    }}
                />
                <div style={{ marginTop: '8px' }}>
                    <MarkdownRender format="md" raw={props.summary?.[0].text ?? ''} {...props.markdownRenderProps} />
                </div>
            </React.Fragment>
        );
    }, []);

    const customRender: DialogueContentItemRendererMap = {
        function_call: {
            create_travel_guide: (item) => {
                return (
                    <div style={functionCallStyle}>
                        Function Tool Call: {item.name} {item.arguments}
                    </div>
                );
            },
        },
        input_text: (item, message) => {
            if (message?.role === 'user') {
                return (
                    <div style={userTextStyle} className="userTextStyle">
                        {item.text}
                    </div>
                );
            }
            return <div style={assistantStyle}>{item.text}</div>;
        },
        reasoning: (item) => {
            return <AIChatDialogue.Reasoning {...item} customRenderer={customRenderReasoningContent} />;
        },
        default: (item, message) => {
            if (message?.role === 'user') {
                return (
                    <div style={userTextStyle} className="userTextStyle">
                        {item}
                    </div>
                );
            } else {
                return <div style={assistantStyle}>{item}</div>;
            }
        },
    };

    return <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} renderDialogueContentItem={customRender} />;
}

消息数据转换

当前组件的对话消息以 OpenAI 的 Response Object 为原型,为了支持用户更好地无缝集成 Chat Completion API 和 Response API,我们提供了四种 Adapter 转换函数,用户可直接使用该函数转换 API 的返回结果,得到可直接用于消息展示的数据,提供两种 Adapter 用于将 ChatInput 组件的数据处理成适配于 Response API 的 input Message 或者 Chat Completion API 中的 Input Message 格式。

import { chatCompletionToMessage, streamingChatCompletionToMessage, responseToMessage, streamingResponseToMessage, chatInputToMessage, chatInputToChatCompletion } from '@fibrous/semi-ai-react';
// 将 Chat Completion API 返回的数据转换为 Chat Dialogue 中的 Message 格式
function chatCompletionToMessage(chatCompletion: ChatCompletion): Message[];

// 将 Chat Completion API 流式返回数据转换为 Chat Dialogue 中的 Message 格式
function streamingChatCompletionToMessage(chatCompletionChunks: ChatCompletionChunk[], state?: StreamingChatState): { messages: Message[]; state?: StreamingChatState };

// 将 Response API 返回的数据转换为 Chat Dialogue 中的 Message 格式
function responseToMessage(response: Response): Message;

// 将 Response API 返回流式数据转换为 Chat Dialogue 中的 Message 格式
// ResponseChunk 是按 type 区分的 Response 流式事件联合类型;chunks 为空时返回 null
function streamingResponseToMessage(chunks: ResponseChunk[], prevState?: StreamingResponseState): { message: Message | null; nextState: StreamingResponseState | null } | null;

// 将 Chat Input 数据转换为 Chat Dialogue 中的 Message 格式,(同 Response API Input Message 格式)
// 返回值不含 id,放进 chats 前需要自行补上
function chatInputToMessage(inputContent: MessageContent): Omit<Message, 'id'>;

// 将 Chat Input 数据转换为 Chat Completion API 中的 Input Message 格式
function chatInputToChatCompletion(inputContent: MessageContent): ChatCompletionInput;

比如,当用户使用 Chat Completion API 接口返回非流式数据时,可以通过 chatCompletionToMessage 函数将 Chat Completion Object 转换为 Dialogue Message 消息块格式。注意,因为 Chat Completion API 可以通过 n 来控制每条输入消息生成多少个结果所以该函数的返回值为数组。(注意:如果 n > 1,用户需要自行决定将哪条数据添加到 message 中展示)

Assistant

Hello! How can I assist you today?

get_current_weather { "location": "Boston, MA" }
代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, chatCompletionToMessage, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';

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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(() => chatCompletionToMessage(CHAT_COMPLETION_DATA));

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />;
}

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',
        },
    ],
    // ...
};

比如,当用户使用 Chat Completion API 接口返回流式数据时,可以通过 streamingChatCompletionToMessage 函数将 Chat Completion Chunk Object List 转换为 Dialogue Message 消息块格式。

代码
import { useState, useCallback, useEffect } from 'react';
import { AIChatDialogue, streamingChatCompletionToMessage, type AIChatDialogueMessage as Message, type StreamingChatState } from '@fibrous/semi-ai-react';

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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState<Message[]>([]);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    useEffect(() => {
        const total = STREAMING_CHAT_COMPLETION_DATA.length;
        let i = 1;
        let state: StreamingChatState | undefined;

        const 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;

            setMessage([partialMessages[0]]);

            i += 1;
        }, 100);

        return () => clearInterval(timer);
    }, []);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />;
}

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',
    },
];

当用户使用 Response API 接口返回非流式数据时,可以通过 responseToMessage 函数将 Response Object 转换为 Dialogue Message 消息块格式。

Assistant
3 篇资料

Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统

get_semi_page {"pageName":"AIChatDialogue"}
代码
import { useState, useCallback } from 'react';
import { AIChatDialogue, responseToMessage, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-react';

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',
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState(() => [responseToMessage(RESPONSE_DATA)]);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />;
}

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"}',
        },
    ],
    // ...
};

当用户使用 Response API 接口返回流式数据时,可以通过 streamingResponseToMessage 函数将 Response Chunk Object List 转换为 Dialogue Message 消息块格式。

代码
import { useState, useCallback, useEffect } from 'react';
import { AIChatDialogue, streamingResponseToMessage, type AIChatDialogueMessage as Message, type ResponseChunk, type StreamingResponseState } from '@fibrous/semi-ai-react';

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,
];

export default function AIChatDialogueDemo() {
    const [messages, setMessage] = useState<Message[]>([]);
    const [currentState, setCurrentState] = useState<StreamingResponseState | undefined>();
    const [currentLength, setCurrentLength] = useState(0);

    const onChatsChange = useCallback((chats: Message[]) => {
        setMessage(chats);
    }, []);

    useEffect(() => {
        if (currentLength > FIXED_SHUFFLED_INDICES.length) {
            return;
        }

        const timer = setTimeout(() => {
            if (currentLength === 0) {
                setCurrentLength(1);
                return;
            }

            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) {
                    setMessage([responseMessage]);
                    setCurrentState(nextState);
                }
            }
            setCurrentLength((prev) => prev + 1);
        }, 200);

        return () => clearTimeout(timer);
    }, [currentLength, currentState]);

    return <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} />;
}

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: {},
        },
    },
];

API 参考

Props

属性说明类型默认值
align对话布局方式,可选 leftRight(左右分布)或 leftAlign(左对齐)。'leftRight' | 'leftAlign'"leftRight"
chats受控对话消息列表,配合 onChatsChange 使用。Message[]-
className根元素的类名。string-
dialogueRenderConfig自定义各区块的渲染,可替换头像、标题、内容、操作栏或整条对话。DialogueRenderConfig-
disabledFileItemClick是否禁用文件卡片的点击。booleanfalse
escapeHtml是否转义用户消息中的 HTML 标签,防止被 Markdown 解析器当作 HTML 处理而丢失内容。booleantrue
hintCls提示区最外层的类名。string-
hints提示信息。点击某条提示后,它成为新的用户消息,并触发 onHintClick。string[]-
hintStyle提示区最外层的样式。React.CSSProperties-
markdownRenderProps透传给每条消息所用 MarkdownRender 的属性,raw 与 ref 由消息决定,不可传入。components 与内置组件合并,同名的(如 code)覆盖内置组件,需要保留默认功能时可用 AIChatDialogue.defaultComponents.code 二次封装;代码块的高亮语言需先通过 CodeHighlight.register 注册。MarkdownRenderProps-
messageEditRender用户消息处于编辑状态时显示的编辑器,参数为消息转换得到的输入内容。Type(props: MessageContent) => ReactNode-
mode对话模式,可选 bubble、noBubble 或 userBubble。'bubble' | 'noBubble' | 'userBubble'"bubble"
onAnnotationClick点击参考来源时调用,参数为被点击消息块的注释数组。Type(annotations: Annotation[]) => void-
onChatsChange消息列表变化时调用,参数为新的消息数组。Type(chats: Message[]) => void-
onFileClick点击文件卡片时调用,参数为该文件。Type(file?: InputFile) => void-
onHintClick点击提示词时调用,参数为该提示文本。Type(hint: string) => void-
onImageClick点击图片时调用,参数为该图片。Type(image?: InputImage) => void-
onMessageBadFeedback对消息点踩时调用,参数为该消息。Type(message?: Message) => void-
onMessageCopy复制消息时调用:文本写入剪贴板成功后才调用,复制的文本不含推理内容;没有可复制的文本时既不写入也不调用。Type(message?: Message) => void-
onMessageDelete删除消息时调用,参数为该消息。Type(message?: Message) => void-
onMessageEdit编辑消息时调用,参数为该消息。Type(message?: Message) => void-
onMessageGoodFeedback对消息点赞时调用,参数为该消息。Type(message?: Message) => void-
onMessageReset重置消息时调用,参数为该消息。Type(message?: Message) => void-
onMessageShare分享消息时调用,参数为该消息。Type(message?: Message) => void-
onReferenceClick引用按钮点击时调用。参数没有 id,加入 AIChatInput 的 references 前由使用方补上;文件项没有 type。Type(item: Partial<Reference>) => void-
onSelect选中的消息变化时调用,参数为选中消息的 id 数组。Type(selectedIds: string[]) => void-
ref接收句柄对象,其上带有下方 Methods 表中的方法,不是 DOM 元素。TypeReact.Ref<AIChatDialogueHandle>-
renderDialogueContentItem按内容项类型返回渲染内容的映射,键是内容项的 type。工具调用先按 type 再按 name 索引(如 function_call 下的 create_travel_guide),纯文本消息使用键 default。DialogueContentItemRendererMap-
renderHintBox自定义提示项的渲染,参数含提示文本 content、序号 index 与点击回调 onHintClick。Type(props: { content: string; index: number; onHintClick: () => void }) => React.ReactNode-
roleConfig各角色(user、assistant、system 等)的名称、头像与头像颜色,必填,不读取全局默认值。RoleConfig-
selecting是否开启多选模式。booleanfalse
showReference是否在文字或文件消息中展示可被引用的图标,仅对用户消息生效。booleanfalse
showReset是否展示重置操作。booleantrue
style根元素的样式。React.CSSProperties-

Methods

方法说明类型
deselectAll取消全选所有消息。Type() => void
scrollToBottom滚动到最底部,参数为 true 时带动画。Type(animation: boolean) => void
scrollToTop滚动到最顶部,参数为 true 时带动画。Type(animation: boolean) => void
selectAll全选所有消息。Type() => void

其他原生属性落在根元素 div 上。根元素带 aria-label 或 aria-labelledby 且没有传 role 时,role 为 group,使用方传入的 role 优先。消息列表是带名称的 role="log" 区域,回复生成期间带 aria-busy="true"。

RoleConfig

属性说明类型默认值
user用户信息Metadata | Map<string, Metadata>-
assistant助手信息Metadata | Map<string, Metadata>-
system系统信息Metadata | Map<string, Metadata>-

MetaData

属性说明类型默认值
name名称string-
avatar头像string-
color头像背景色,同 Avatar 组件的 color 参数, 支持 amber、 blue、 cyan、 green、 grey、 indigo、 light-blue、 light-green、 lime、 orange、 pink、 purple、 red、 teal、 violet、 yellowAvatarColorgrey

Message

属性说明类型默认值
role角色string-
name名称string-
id唯一标识string-
content消息内容string| ContentItem[]-
createdAt创建时间number-
model模型名称string-
status消息状态,可选值为 queued | in_progress | incomplete | completed | failed | cancelledstringcompleted
editing是否处于编辑状态,由组件在编辑时写入boolean-
like是否已点赞,由组件在点赞时写入boolean-
dislike是否已点踩,由组件在点踩时写入boolean-
references当前消息引用的文件或文本Reference[]-
error流式响应的错误信息,由 streamingResponseToMessage 写入{ code?: string; message?: string } | null-

Reference

与 AIChatInput 的 Reference 是同一类型,id、type 必填,其他字段原样保留。

属性说明类型默认值
id唯一标识,必填string-
type类型,必填string-
name名称string-
url地址string-
content文本内容string-

Methods

方法列在上方 API 参考的 Methods 表中。通过 ref 拿到句柄后调用:

import { useRef } from 'react';
import { AIChatDialogue, type AIChatDialogueHandle } from '@fibrous/semi-ai-react';

const dialogue = useRef<AIChatDialogueHandle>(null);
const toBottom = () => dialogue.current?.scrollToBottom(true);

<AIChatDialogue ref={dialogue} chats={messages} roleConfig={roleConfig} onChatsChange={setMessages} />;

Static Properties

属性说明类型
defaultComponents默认的 Markdown 渲染组件集合,包含增强版的 Code 组件,支持代码语言标识和复制功能。可用于 markdownRenderProps.components 的二次封装{ code: React.ComponentType }
Reasoning推理块组件,API 见下方 AIChatDialogue.ReasoningReact.ComponentType
Step步骤展示组件,API 见下方 AIChatDialogue.StepReact.ComponentType
Annotation参考来源组件,API 见下方 AIChatDialogue.AnnotationReact.ComponentType

使用示例:

当需要在自定义代码块渲染时保留默认功能,可以通过 AIChatDialogue.defaultComponents.code 获取默认组件:

import { AIChatDialogue } from '@fibrous/semi-ai-react';

function CustomCodeDemo() {
    return (
        <AIChatDialogue
            chats={messages}
            roleConfig={roleConfig}
            markdownRenderProps={{
                components: {
                    code: (props) => {
                        // 获取默认的 Code 组件
                        const DefaultCode = AIChatDialogue.defaultComponents.code;
                        // 可以在此处添加自定义逻辑
                        return <DefaultCode {...props} />;
                    },
                },
            }}
        />
    );
}

AIChatDialogue.Reasoning

Props

属性说明类型默认值
content推理内容,summary 为空时按换行拼接其中的文本作为推理正文。{ text?: string; type?: string }[]-
customRenderer替换推理正文的渲染。参数为推理组件的全部属性,返回的内容放在正文区域中。Type(props: ReasoningWidgetProps) => React.ReactNode-
markdownRenderProps透传给推理正文所用 MarkdownRender 的属性,raw 由推理正文决定。MarkdownRenderProps-
status推理状态。为 completed 时标题显示已完成,且初次渲染时折叠;其他值显示思考中并展开。string-
summary推理摘要,非空时按换行拼接其中的文本作为推理正文。{ text?: string; type?: string }[]-

className、style、ref 与原生属性落在根元素 div 上。

AIChatDialogue.Step

Props

属性说明类型默认值
steps步骤列表,必填。每个步骤含 summary、status 与 actions,status 为 completed 时显示完成图标,否则显示加载中;actions 每项含 summary、description 与 icon,步骤有操作时可点击或用 Enter、空格展开收起。Step[]-

className、style、ref 与原生属性落在根元素 div 上。

AIChatDialogue.Annotation

Props

属性说明类型默认值
annotation参考来源列表,必填。每项的 logo 显示为来源图标,title 作为图标的替代文字;url 与 detail 不做渲染,随点击回调一起传出。AnnotationItemProps[] | Annotation[]-
description来源图标后的描述文字,不传时显示来源数量。string-
maxCount最多显示的来源图标数,超出部分显示为 +N。必填。number-
onClick点击或用 Enter、空格激活时调用,参数为事件与参考来源列表。Type(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>, item: AnnotationItemProps[]) => void-

className、style、ref 与原生属性落在根元素 div 上。

ContentItem

ContentItem 支持所有 OpenAI Response InputItem 和 OutputItem 类型,具体类型定义如下

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
}

// 输入条目
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
}


// 输出条目
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
}

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$color-aiChatDialogue_wrapper-selected-bgvar(--semi-color-fill-0)外层选中背景色
$color-aiChatDialogue_loading_circle_first-bgrgba(83, 56, 255, 1)第一个加载圆点背景色
$color-aiChatDialogue_loading_circle_second-bgrgba(176, 48, 240, 1)第二个加载圆点背景色
$color-aiChatDialogue_loading_circle_third-bgrgba(231, 45, 255, 1)第三个加载圆点背景色
$color-aiChatDialogue_loading_textvar(--semi-color-text-3)加载中文本颜色
$color-aiChatDialogue_bubble-bgvar(--semi-color-fill-0)对话气泡背景色
$color-aiChatDialogue_file-bgvar(--semi-color-fill-0)文件组件背景色
$color-aiChatDialogue_file_title-textvar(--semi-color-text-0)文件标题文本颜色
$color-aiChatDialogue_file_metadata-textvar(--semi-color-text-2)文件元数据文本颜色
$color-aiChatDialogue_tool_call-bgvar(--semi-color-fill-0)工具调用组件背景色
$color-aiChatDialogue_tool_call-textvar(--semi-color-text-2)工具调用组件文本颜色
$color-aiChatDialogue_action_dropdown-textvar(--semi-color-danger)操作区下拉菜单文本颜色
$color-aiChatDialogue_reasoning-bordervar(--semi-color-border)推理组件边框色
$color-aiChatDialogue_step-bordervar(--semi-color-border)步骤组件边框色
$color-aiChatDialogue_step_action-bgvar(--semi-color-fill-0)步骤组件 action 背景色
$color-aiChatDialogue_step_completed-textvar(--semi-color-success)步骤组件完成文本颜色
$color-aiChatDialogue_step_suffix-textvar(--semi-color-text-1)步骤组件后缀文本颜色
$color-aiChatDialogue_step_action-textvar(--semi-color-text-2)步骤组件 action 文本颜色
$color-aiChatDialogue_annotation-bordervar(--semi-color-border)参考来源组件边框色
$color-aiChatDialogue_annotation-bg-hovervar(--semi-color-fill-0)参考来源组件悬停背景色
$color-aiChatDialogue_annotation-textvar(--semi-color-text-2)参考来源组件文本颜色
$color-aiChatDialogue_reference-bgvar(--semi-color-fill-0)引用组件背景色
$color-aiChatDialogue_reference-textvar(--semi-color-text-2)引用组件文本颜色
$color-aiChatDialogue_icon_reference-textvar(--semi-color-text-0)用户消息引用图标颜色
$color-aiChatDialogue_code-bordervar(--semi-color-border)代码块边框色
$color-aiChatDialogue_code_topSlot-bgvar(--semi-color-fill-0)代码块顶部槽背景色
$color-aiChatDialogue_code_topSlot-textvar(--semi-color-text-0)代码块顶部槽文本颜色
$color-aiChatDialogue_hint-bgvar(--semi-color-bg-0)提示项背景色
$color-aiChatDialogue_hint-bordervar(--semi-color-border)提示项边框色
$color-aiChatDialogue_hint-bg-hovervar(--semi-color-fill-0)提示项悬停背景色
$color-aiChatDialogue_hint-textvar(--semi-color-text-0)提示项文本颜色
$color-aiChatDialogue_code_icon-bgvar(--semi-teal-4)code icon 背景色
$color-aiChatDialogue_word_icon-bgvar(--semi-blue-4)word icon 背景色
$color-aiChatDialogue_excel_icon-bgvar(--semi-green-4)excel icon 背景色
$color-aiChatDialogue_pdf_icon-bgvar(--semi-red-4)pdf icon 背景色
$color-aiChatDialogue_video_icon-bgvar(--semi-purple-4)video icon 背景色
$color-aiChatDialogue_default_icon-bgvar(--semi-grey-4)default icon 背景色
$color-aiChatDialogue_icon-textvar(--semi-color-white)icon 文本颜色
$color-aiChatDialogue_failedvar(--semi-color-danger)失败提示颜色
$color-aiChatDialogue_code_content-bgvar(--semi-color-bg-0)code content 背景色
$width-aiChatDialogue_wrapper100%外层宽度
$width-aiChatDialogue_inner100%气泡右侧展示内层宽度
$width-aiChatDialogue_backBottom_button42px返回按钮宽度
$height-aiChatDialogue_backBottom_button42px返回按钮高度
$width-aiChatDialogue_avatar24px头像宽度
$height-aiChatDialogue_avatar24px头像高度
$width-aiChatDialogue_content_editing100%内容宽度 - 编辑状态
$width-aiChatDialogue_content_inner100%内容内层宽度
$width-aiChatDialogue_content_max100%内容最大宽度
$width-aiChatDialogue_bubble_max100%气泡最大宽度
$width-aiChatDialogue_loading_circle3px加载圆点宽度
$height-aiChatDialogue_loading_circle3px加载圆点高度
$width-aiChatDialogue_img256px图片宽度
$height-aiChatDialogue_img256px图片高度
$width-aiChatDialogue_img_list128px图片列表项宽度
$height-aiChatDialogue_img_list128px图片列表项高度
$width-aiChatDialogue_file240px文件组件宽度(含内边距)
$width-aiChatDialogue_file_icon_wrapper36px文件图标外层宽度
$height-aiChatDialogue_file_icon_wrapper36px文件图标外层高度
$width-aiChatDialogue_action_btn24px操作按钮宽度
$height-aiChatDialogue_action_btn24px操作按钮高度
$width-aiChatDialogue_reasoning100%推理组件宽度
$width-aiChatDialogue_reasoning_wrapper100%推理组件外层宽度
$width-aiChatDialogue_reasoning-border1px推理组件 border 宽度
$width-aiChatDialogue_reasoning_prefix20px推理前缀图标宽度
$height-aiChatDialogue_reasoning_prefix20px推理前缀图标高度
$width-aiChatDialogue_step-border1px步骤组件 border 宽度
$width-aiChatDialogue_step_prefix20px步骤前缀图标宽度
$height-aiChatDialogue_step_prefix20px步骤前缀图标高度
$width-aiChatDialogue_step_suffix20px步骤后缀图标宽度
$height-aiChatDialogue_step_suffix20px步骤后缀图标高度
$width-aiChatDialogue_step_line1px步骤左侧线宽度
$width-aiChatDialogue_annotation100%参考来源宽度
$width-aiChatDialogue_annotation-border1px参考来源组件 border 宽度
$width-aiChatDialogue_annotation_logo16px参考来源logo宽度
$height-aiChatDialogue_annotation_logo16px参考来源logo高度
$width-aiChatDialogue_reference_icon16px引用图标宽度
$height-aiChatDialogue_reference_icon16px引用图标高度
$width-aiChatDialogue_reference_max200px引用组件最大宽度
$width-aiChatDialogue_reference_name_max130px引用名称最大宽度
$width-aiChatDialogue_code-border1px代码块组件 border 宽度
$width-aiChatDialogue_hint-border1px提示项组件 border 宽度
$width-aiChatDialogue_file_title_max180px文件标题最大宽度
$width-aiChatDialogue_file_title_max-hover140px用户消息悬停时文件标题最大宽度(显示引用图标时)
$width-aiChatDialogue_action_copy24px复制按钮宽度
$spacing-aiChatDialogue_wrapper-paddingY8px最外层垂直内边距
$spacing-aiChatDialogue_wrapper-paddingX16px最外层水平内边距
$spacing-aiChatDialogue_wrapper-marginTop2px最外层顶部外边距
$spacing-aiChatDialogue_wrapper-columnGap16px最外层列间距
$spacing-aiChatDialogue_container-columnGap12pxcontainer 容器列间距
$spacing-aiChatDialogue_content_failed-marginRight8px内容失败提示右外边距
$spacing-aiChatDialogue_content_bubble-marginTop8px内容气泡顶部外边距
$spacing-aiChatDialogue_content_noBubble-marginTop8px内容无气泡顶部外边距
$spacing-aiChatDialogue_bubble-paddingY12px气泡垂直内边距
$spacing-aiChatDialogue_bubble-paddingX16px气泡水平内边距
$spacing-aiChatDialogue_content_loading-marginTop8px内容加载顶部外边距
$spacing-aiChatDialogue_loading_item-marginY1px加载项垂直外边距
$spacing-aiChatDialogue_loading_item-marginX1px加载项水平外边距
$spacing-aiChatDialogue_loading_text-marginLeft8px加载文本左外边距
$spacing-aiChatDialogue_img-marginTop8px图片顶部外边距
$spacing-aiChatDialogue_img_list-marginRight4px图片列表右外边距
$spacing-aiChatDialogue_file-paddingY8px文件组件垂直内边距
$spacing-aiChatDialogue_file-paddingX8px文件组件水平内边距
$spacing-aiChatDialogue_file-columnGap8px文件组件列间距
$spacing-aiChatDialogue_file-marginTop8px文件组件顶部外边距
$spacing-aiChatDialogue_file-marginRight8px文件组件右外边距
$spacing-aiChatDialogue_tool_call-paddingY6px工具调用垂直内边距
$spacing-aiChatDialogue_tool_call-paddingX8px工具调用水平内边距
$spacing-aiChatDialogue_tool_call-columnGap4px工具调用列间距
$spacing-aiChatDialogue_tool_call-marginTop8px工具调用顶部外边距
$spacing-aiChatDialogue_custom_renderer-marginTop8px自定义渲染器顶部外边距
$spacing-aiChatDialogue_action-marginTop8px操作顶部外边距
$spacing-aiChatDialogue_reasoning_wrapper-marginTop8px推理组件外层顶部外边距
$spacing-aiChatDialogue_reasoning_header-padding16px推理头部内边距
$spacing-aiChatDialogue_reasoning_content-paddingY12px推理内容垂直内边距
$spacing-aiChatDialogue_reasoning_content-paddingX16px推理内容水平内边距
$spacing-aiChatDialogue_step_wrapper-marginTop4px步骤外层顶部外边距
$spacing-aiChatDialogue_step-paddingY6px步骤垂直内边距
$spacing-aiChatDialogue_step-paddingX12px步骤水平内边距
$spacing-aiChatDialogue_step-marginTop12px步骤顶部外边距
$spacing-aiChatDialogue_step_prefix-marginRight6px步骤前缀右外边距
$spacing-aiChatDialogue_step_suffix-marginLeft8px步骤后缀左外边距
$spacing-aiChatDialogue_step_panel-gap12px步骤面板间距
$spacing-aiChatDialogue_step_line-marginLeft20px步骤连接线左外边距
$spacing-aiChatDialogue_step_action-marginTop12px步骤 action 顶部外边距
$spacing-aiChatDialogue_step_action_summary-marginBottom4px步骤 action 摘要底部外边距
$spacing-aiChatDialogue_step_action_desc-paddingY6px步骤 action 描述垂直内边距
$spacing-aiChatDialogue_step_action_desc-paddingX8px步骤 action 描述水平内边距
$spacing-aiChatDialogue_step_action_desc-columnGap4px步骤 action 描述列间距
$spacing-aiChatDialogue_annotation_wrapper-marginTop8px参考来源外层顶部外边距
$spacing-aiChatDialogue_annotation_wrapper-paddingY8px参考来源外层垂直内边距
$spacing-aiChatDialogue_annotation_wrapper-paddingX12px参考来源外层水平内边距
$spacing-aiChatDialogue_annotation_content-columnGap8px参考来源内容列间距
$spacing-aiChatDialogue_references-marginTop8px引用顶部外边距
$spacing-aiChatDialogue_references-columnGap4px引用列间距
$spacing-aiChatDialogue_references-rowGap4px引用行间距
$spacing-aiChatDialogue_reference-paddingY8px引用垂直内边距
$spacing-aiChatDialogue_reference-paddingX12px引用水平内边距
$spacing-aiChatDialogue_reference-columnGap8px引用列间距
$spacing-aiChatDialogue_reference_icon-marginRight4px引用图标右外边距
$spacing-aiChatDialogue_reference_img-marginRight4px引用图片右外边距
$spacing-aiChatDialogue_icon_reference-paddingY0px用户消息引用图标垂直内边距
$spacing-aiChatDialogue_icon_reference-paddingX8px用户消息引用图标水平内边距
$spacing-aiChatDialogue_code_topSlot-paddingY8px代码块顶部槽垂直内边距
$spacing-aiChatDialogue_code_topSlot-paddingX16px代码块顶部槽水平内边距
$spacing-aiChatDialogue_hints-rowGap8px提示行间距
$spacing-aiChatDialogue_hints-marginTop4px提示顶部外边距
$spacing-aiChatDialogue_hints-marginBottom12px提示底部外边距
$spacing-aiChatDialogue_hints-marginLeft52px提示左外边距
$spacing-aiChatDialogue_hint_item-paddingY8px提示项垂直内边距
$spacing-aiChatDialogue_hint_item-paddingX16px提示项水平内边距
$spacing-aiChatDialogue_hints_selecting-marginLeft68px提示项选择状态下左外边距
$radius-aiChatDialogue_backBottom_button50%返回按钮圆角
$radius-aiChatDialogue_wrapper_selected16px外层选中圆角
$radius-aiChatDialogue_loading_circlevar(--semi-border-radius-circle)加载圆点圆角
$radius-aiChatDialogue_bubblevar(--semi-border-radius-large)对话气泡圆角
$radius-aiChatDialogue_imgvar(--semi-border-radius-medium)图片圆角
$radius-aiChatDialogue_filevar(--semi-border-radius-medium)文件组件圆角
$radius-aiChatDialogue_icon_wrappervar(--semi-border-radius-extra-small)图标外层圆角
$radius-aiChatDialogue_tool_callvar(--semi-border-radius-medium)工具调用组件圆角
$radius-aiChatDialogue_reasoning_wrappervar(--semi-border-radius-large)推理组件外层圆角
$radius-aiChatDialogue_stepvar(--semi-border-radius-medium)步骤组件圆角
$radius-aiChatDialogue_action_descvar(--semi-border-radius-medium)操作描述圆角
$radius-aiChatDialogue_annotation_wrappervar(--semi-border-radius-medium)参考来源外层圆角
$radius-aiChatDialogue_referencevar(--semi-border-radius-medium)引用组件圆角
$radius-aiChatDialogue_reference_icon2px引用图标圆角
$radius-aiChatDialogue_codevar(--semi-border-radius-large)代码块圆角
$radius-aiChatDialogue_hint_itemvar(--semi-border-radius-medium)提示项圆角
$font-aiChatDialogue_title-fontSize$font-size-header-6标题字体大小
$font-aiChatDialogue_title-fontWeight$font-weight-regular标题字体粗细
$font-aiChatDialogue_title-lineHeight20px标题行高
$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-lineHeight22px推理头部标题行高
$font-aiChatDialogue_dropdown_icon-fontSize$font-size-header-6下拉菜单图标字体大小
$font-aiChatDialogue_step-fontSize$font-size-regular步骤字体大小
$font-aiChatDialogue_step-lineHeight20px步骤行高
$font-aiChatDialogue_step_action-fontSize$font-size-regular步骤 action 字体大小
$font-aiChatDialogue_step_action-lineHeight20px步骤 action 行高
$font-aiChatDialogue_annotation_content-fontSize$font-size-regular参考来源内容字体大小
$font-aiChatDialogue_references-fontSize12px引用字体大小
$font-aiChatDialogue_references-lineHeight$font-size-header-6引用行高
$font-aiChatDialogue_code_topSlot-fontSize$font-size-regular代码块顶部槽字体大小
$font-aiChatDialogue_code-lineHeight32px代码块行高
$font-aiChatDialogue_hint_content-fontSize$font-size-regular提示内容字体大小
$z-aiChatDialogue_backBottom1返回按钮z-index