AI Component · AIChatDialogue

AIChatDialogue

Display AI chat conversation messages to users

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

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

Basic Usage

Set chats and onChatsChange to enable basic conversation display and interactions. When updating, pass a new message array: when the array is held in a createSignal, mutating it in place (such as push) does not re-render, so replace it with a new array.

Use align to control layout: leftRight (default) and 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;
Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-solid';
import { Radio, RadioGroup, type RadioChangeEvent } from '@fibrous/semi-ui-solid';

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

const containerStyle = { display: 'flex', 'flex-direction': 'column', 'row-gap': '8px' } as const;
const rowStyle = { display: 'flex', 'align-items': 'center', 'column-gap': '10px' } as const;
const panelStyle = { border: '1px solid var(--semi-color-border)', 'border-radius': '12px', 'margin-top': '10px', padding: '20px' } as const;

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

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

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

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

    return (
        <>
            <span style={containerStyle}>
                <span style={rowStyle}>
                    模式
                    <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={rowStyle}>
                    会话布局方式
                    <RadioGroup aria-label="会话布局方式" onChange={onAlignChange} value={align()} type="button">
                        <Radio value="leftRight">左右分布</Radio>
                        <Radio value="leftAlign">左对齐</Radio>
                    </RadioGroup>
                </span>
            </span>
            <div style={panelStyle}>
                <AIChatDialogue align={align()} mode={mode()} chats={messages()} roleConfig={roleConfig} onChatsChange={onChatsChange} />
            </div>
        </>
    );
}

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.

Assistant

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

Code
import { createSignal, onCleanup } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';
import { Button } from '@fibrous/semi-ui-solid';

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] = createSignal(defaultMessages);
    const [streaming, setStreaming] = createSignal(false);
    let timer: ReturnType<typeof setInterval> | undefined;

    onCleanup(() => clearInterval(timer));

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

    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;
        timer = 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(timer);
                setStreaming(false);
            }
        }, 100);
    };

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

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).

Assistant

请求成功

Assistant
请稍候...
Assistant

请求错误

Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';

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, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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).

Assistant

普通文本

User

帮我生成类似的图片

图片

以下是文件展示

semi.pdf
semi.json
semi.docx
Assistant

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

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

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

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

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

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

function stepIcon(type?: string) {
    switch (type) {
        case 'search':
            return <IconSearchStroked />;
        case 'docs':
            return <IconBriefStroked />;
        case 'code':
            return <IconCodeStroked />;
        default:
            return undefined;
    }
}

function toActions(steps: PlanStep[] = []) {
    return steps.map((item) => ({
        summary: item.summary,
        description: item.description,
        icon: stepIcon(item.type),
    }));
}

const customRender: DialogueContentItemRendererMap = {
    plan: (item) => {
        const steps = (item.content as PlanBlock[]).map((plan) => ({
            summary: plan.summary,
            actions: toActions(plan.steps),
            status: 'completed',
        }));
        return <AIChatDialogue.Step steps={steps} />;
    },
};

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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. onReferenceClick configures the click handler for the reference button.

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

当前消息为引用 demo 的示例

Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';

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

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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

Selection

会话布局方式
是否开启选择
选择方式
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;
Code
import { createSignal, onMount } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueElement, type AIChatDialogueMessage as Message, type AIChatDialogueProps } from '@fibrous/semi-ai-solid';
import { Radio, RadioGroup, type RadioChangeEvent } from '@fibrous/semi-ui-solid';

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

const containerStyle = { display: 'flex', 'flex-direction': 'column', 'row-gap': '8px' } as const;
const rowStyle = { display: 'flex', 'align-items': 'center', 'column-gap': '10px' } as const;
const panelStyle = { border: '1px solid var(--semi-color-border)', 'border-radius': '12px', 'margin-top': '10px', padding: '20px' } as const;

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

export default function AIChatDialogueDemo() {
    let dialogue: AIChatDialogueElement | undefined;
    const [messages] = createSignal(defaultMessages);
    const [align, setAlign] = createSignal<AIChatDialogueProps['align']>('leftRight');
    const [select, setSelect] = createSignal(true);
    const [selection, setSelection] = createSignal('allSelect');

    onMount(() => {
        dialogue?.selectAll();
    });

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

    const onSelectionChange = (e: RadioChangeEvent) => {
        if (e.target.value === 'allSelect') {
            dialogue?.selectAll();
        } else {
            dialogue?.deselectAll();
        }
        setSelection(e.target.value);
    };

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

    return (
        <div>
            <span style={containerStyle}>
                <span style={rowStyle}>
                    会话布局方式
                    <RadioGroup onChange={onAlignChange} value={align()} type="button">
                        <Radio value="leftRight">左右分布</Radio>
                        <Radio value="leftAlign">左对齐</Radio>
                    </RadioGroup>
                </span>
                <span style={rowStyle}>
                    是否开启选择
                    <RadioGroup onChange={onSelectChange} value={select()} type="button">
                        <Radio value={true}>开启</Radio>
                        <Radio value={false}>关闭</Radio>
                    </RadioGroup>
                </span>
                <span style={rowStyle}>
                    选择方式
                    <RadioGroup onChange={onSelectionChange} value={selection()} type="button">
                        <Radio value="allSelect">全选</Radio>
                        <Radio value="cancelSelect">取消全选</Radio>
                    </RadioGroup>
                </span>
            </span>

            <div style={panelStyle}>
                <AIChatDialogue ref={(element) => (dialogue = element)} align={align()} mode="bubble" chats={messages()} selecting={select()} onSelect={onSelect} roleConfig={roleConfig} />
            </div>
        </div>
    );
}

Hints

Use hints to set the hint area content. Clicking a hint populates it as the new user input and triggers onHintClick.

Assistant

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

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

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

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);
    const [hints, setHints] = createSignal(hintsExample);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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

Custom Hint Rendering

Customize the hint area with renderHintBox. Parameters:

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

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

Code
import { type JSX, createSignal } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';
import { IconArrowRight } from '@fibrous/semi-icons-solid';

const commonHintStyle: JSX.CSSProperties = {
    border: '1px solid var(--semi-color-border)',
    padding: '10px',
    'border-radius': '10px',
    'background-color': 'transparent',
    color: 'var( --semi-color-text-1)',
    display: 'flex',
    'justify-content': 'space-between',
    'align-items': 'center',
    cursor: 'pointer',
    font: 'inherit',
    'font-size': '14px',
};

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

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);
    const [hints, setHints] = createSignal(hintsExample);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

    return (
        <AIChatDialogue
            align="leftRight"
            mode="bubble"
            chats={messages()}
            roleConfig={roleConfig}
            onChatsChange={onChatsChange}
            hints={hints()}
            onHintClick={onHintClick}
            renderHintBox={(props) => (
                <button type="button" style={commonHintStyle} onClick={props.onHintClick}>
                    {props.content}
                    <IconArrowRight style={{ 'margin-left': '10px' }} />
                </button>
            )}
        />
    );
}

Custom Chat Box Rendering

import type { JSX } from 'solid-js';

export interface RenderTitleProps {
    message?: Message;
    role?: Metadata;
    /* Creates the default title; call it where it is inserted */
    defaultTitle?: () => JSX.Element;
}

export interface RenderAvatarProps {
    message?: Message;
    role?: Metadata;
    /* Creates the default avatar; call it where it is inserted */
    defaultAvatar?: () => JSX.Element;
}

export interface RenderContentProps {
    message?: Message;
    role?: Metadata;
    /* Creates the default content; call it where it is inserted */
    defaultContent?: () => JSX.Element;
    class?: string;
}

/* Render functions of the buttons the default action bar shows; a key is missing when its button is not shown (e.g. an unfinished message). Call one where it is inserted */
export interface DefaultActionNodeObj {
    copyNode?: () => JSX.Element;
    dislikeNode?: () => JSX.Element;
    editNode?: () => JSX.Element;
    likeNode?: () => JSX.Element;
    moreNode?: () => JSX.Element;
    resetNode?: () => JSX.Element;
    shareNode?: () => JSX.Element;
}

export interface RenderActionProps {
    message?: Message;
    /* Render functions of the default buttons in display order; call each where it is inserted */
    defaultActions?: (() => JSX.Element)[];
    class: string;
    defaultActionsObj?: DefaultActionNodeObj;
}

/* Render functions of the default nodes; call one where it is inserted, and a node that is never called is never created */
export interface FullDialogueNodes {
    avatar?: () => JSX.Element;
    title?: () => JSX.Element;
    content?: () => JSX.Element;
    action?: () => JSX.Element;
}

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

export interface DialogueRenderConfig {
    /* Customize action bar rendering */
    renderDialogueAction?: (props: RenderActionProps) => JSX.Element;
    /* Customize avatar rendering */
    renderDialogueAvatar?: (props: RenderAvatarProps) => JSX.Element;
    /* Customize content rendering */
    renderDialogueContent?: (props: RenderContentProps) => JSX.Element;
    /* Customize message title rendering */
    renderDialogueTitle?: (props: RenderTitleProps) => JSX.Element;
    /* Fully customize the entire chat box */
    renderFullDialogue?: (props: RenderFullDialogueProps) => JSX.Element;
}

Customize the avatar and title via renderDialogueAvatar and renderDialogueTitle. The render function arguments, such as role, message, defaultActions and class, are accessors: read them directly in JSX (for example props.role?.name) and do not destructure them. defaultTitle, defaultAvatar, defaultContent and the items of defaultNodes are render functions: call one where it is inserted (for example {props.defaultTitle?.()}), and a node that is never called is never created.

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;
Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, type AIChatDialogueMessage as Message, type DialogueRenderConfig } from '@fibrous/semi-ai-solid';
import { Avatar } from '@fibrous/semi-ui-solid';

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

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

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

Custom Message Content Rendering

Use renderDialogueContentItem to provide renderers by message type. The keys are the type of each content item: a tool call is indexed by its type and then by its name (such as create_travel_guide under function_call), and a plain-text message is rendered with the key default. Inside the renderers you can reuse AIChatDialogue.Reasoning and AIChatDialogue.Annotation; see Static Properties below. Example:

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

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

const userTextStyle: JSX.CSSProperties = {
    'background-color': 'var(--semi-color-fill-1)',
    color: 'var(--semi-color-text-0)',
    'border-radius': '25px',
    padding: '6px 16px',
};

const assistantStyle: JSX.CSSProperties = {
    color: 'var(--semi-color-text-0)',
    padding: '6px 16px',
};

const functionCallStyle: JSX.CSSProperties = {
    'background-color': 'var(--semi-color-fill-1)',
    padding: '6px 16px',
    'border-radius': '25px',
};

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) =>
        message?.role === 'user' ? (
            <div style={userTextStyle} class="userTextStyle">
                {item.text}
            </div>
        ) : (
            <div style={assistantStyle}>{item.text}</div>
        ),
    reasoning: (item) => {
        // the widget takes its annotations from the item; the item is read here instead of spreading it into the props
        const annotations: Annotation[] = item.annotations ?? [];
        return (
            <AIChatDialogue.Reasoning
                status={item.status}
                summary={item.summary}
                customRenderer={() => (
                    <>
                        <AIChatDialogue.Annotation
                            annotation={annotations}
                            description="参考资料"
                            maxCount={3}
                            onClick={(e) => {
                                e.stopPropagation();
                                Toast.success('Ready to open the sidebar!');
                            }}
                        />
                        <div style={{ 'margin-top': '8px' }}>
                            <MarkdownRender format="md" raw={item.summary?.[0]?.text ?? ''} />
                        </div>
                    </>
                )}
            />
        );
    },
    default: (item, message) =>
        message?.role === 'user' ? (
            <div style={userTextStyle} class="userTextStyle">
                {item}
            </div>
        ) : (
            <div style={assistantStyle}>{item}</div>
        ),
};

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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-solid';
// 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.)

Assistant

Hello! How can I assist you today?

get_current_weather { "location": "Boston, MA" }
Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, chatCompletionToMessage, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';

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

export default function AIChatDialogueDemo() {
    const [messages, setMessages] = createSignal<Message[]>(chatCompletionToMessage(CHAT_COMPLETION_DATA));

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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
import { createSignal, onCleanup, onMount } from 'solid-js';
import { AIChatDialogue, streamingChatCompletionToMessage, type AIChatDialogueMessage as Message, type StreamingChatState } from '@fibrous/semi-ai-solid';

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, setMessages] = createSignal<Message[]>([]);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

    onMount(() => {
        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;

            setMessages([partialMessages[0]]);

            i += 1;
        }, 100);

        onCleanup(() => 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',
    },
];

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.

Assistant
3 篇资料

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

get_semi_page {"pageName":"AIChatDialogue"}
Code
import { createSignal } from 'solid-js';
import { AIChatDialogue, responseToMessage, type AIChatDialogueMessage as Message } from '@fibrous/semi-ai-solid';

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 defaultMessages: Message[] = [responseToMessage(RESPONSE_DATA)];

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, setMessages] = createSignal(defaultMessages);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

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

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
import { batch, createEffect, createSignal, onCleanup } from 'solid-js';
import { AIChatDialogue, streamingResponseToMessage, type AIChatDialogueMessage as Message, type ResponseChunk, type StreamingResponseState } from '@fibrous/semi-ai-solid';

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, setMessages] = createSignal<Message[]>([]);
    const [currentState, setCurrentState] = createSignal<StreamingResponseState | undefined>(undefined);
    const [currentLength, setCurrentLength] = createSignal(0);

    const onChatsChange = (chats: Message[]) => {
        setMessages(chats);
    };

    // the state updates are batched: each one re-runs this effect, and a run per update would start a timer per update
    createEffect(() => {
        const length = currentLength();
        const state = currentState();
        if (length > FIXED_SHUFFLED_INDICES.length) {
            return;
        }

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

            const currentIndices = FIXED_SHUFFLED_INDICES.slice(0, length);
            const currentChunks = currentIndices.map((index) => REASONING_CHUNKS[index]);

            const result = streamingResponseToMessage(currentChunks, state);

            batch(() => {
                if (result) {
                    const { message: responseMessage, nextState } = result;
                    if (responseMessage) {
                        setMessages([responseMessage]);
                        setCurrentState(nextState ?? undefined);
                    }
                }
                setCurrentLength(length + 1);
            });
        }, 200);

        onCleanup(() => clearTimeout(timer));
    });

    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 Reference

Props

PropertyDescriptionTypeDefault
alignConversation layout: leftRight or leftAlign.'leftRight' | 'leftAlign'"leftRight"
chatsControlled list of conversation messages; use it together with onChatsChange.Message[]-
dialogueRenderConfigCustom rendering of the dialogue parts: avatar, title, content, action bar or the whole dialogue.DialogueRenderConfig-
disabledFileItemClickWhether clicking a file card is disabled.booleanfalse
escapeHtmlWhether to escape HTML tags in user messages, so the Markdown parser does not treat them as HTML and drop content.booleantrue
hintClsClass name of the outermost hint area element.string-
hintsHint texts. Clicking a hint turns it into a new user message and calls onHintClick.string[]-
hintStyleStyle of the outermost hint area element.JSX.CSSProperties | string-
markdownRenderPropsProps 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-
messageEditRenderThe editor shown while a user message is being edited; its parameter is the input content converted from the message.Type(props: MessageContent) => JSX.Element-
modeConversation mode: bubble, noBubble or userBubble.'bubble' | 'noBubble' | 'userBubble'"bubble"
onAnnotationClickCalled when the references of a message block are clicked, with that block's annotations.Type(annotations: Annotation[]) => void-
onChatsChangeCalled when the message list changes, with the new message array.Type(chats: Message[]) => void-
onFileClickCalled when a file card is clicked, with the file.Type(file?: InputFile) => void-
onHintClickCalled when a hint is clicked, with the hint text.Type(hint: string) => void-
onImageClickCalled when an image is clicked, with the image.Type(image?: InputImage) => void-
onMessageBadFeedbackCalled when a message is disliked, with the message.Type(message?: Message) => void-
onMessageCopyCalled 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.Type(message?: Message) => void-
onMessageDeleteCalled when a message is deleted, with the message.Type(message?: Message) => void-
onMessageEditCalled when a message is edited, with the message.Type(message?: Message) => void-
onMessageGoodFeedbackCalled when a message is liked, with the message.Type(message?: Message) => void-
onMessageResetCalled when a message is reset, with the message.Type(message?: Message) => void-
onMessageShareCalled when a message is shared, with the message.Type(message?: Message) => void-
onReferenceClickCalled when a reference button is clicked. The argument 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>) => void-
onSelectCalled when the selected messages change, with the ids of the selected messages.Type(selectedIds: string[]) => void-
refReceives the root div element, which carries the methods listed in the Methods table.TypeRef<AIChatDialogueElement>-
renderDialogueContentItemRenderers keyed by content item type. A tool call is indexed by its name inside its type, and a plain-text message uses the key default.DialogueContentItemRendererMap-
renderHintBoxCustom rendering of each hint item, with the hint text content, its index and the click callback onHintClick.Type(props: RenderHintBoxProps) => JSX.Element-
roleConfigName, avatar and avatar color of each role (user, assistant, system and so on). Required, and not read from the global defaults.RoleConfig-
selectingWhether multi-select mode is on.booleanfalse
showReferenceWhether to show the reference icon on text or file messages. Only user messages are affected.booleanfalse
showResetWhether to show the reset action.booleantrue

Methods

MethodDescriptionType
deselectAllDeselects all messages.Type() => void
scrollToBottomScrolls to the bottom, animated when the argument is true.Type(animation: boolean) => void
scrollToTopScrolls to the top, animated when the argument is true.Type(animation: boolean) => void
selectAllSelects all messages.Type() => void

class, style, ref and native attributes land on the root div element. When the root has aria-label or aria-labelledby and no role is passed, 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

PropDescriptionTypeDefault
useruser infoMetadata | Map<string, Metadata>-
assistantassistant infoMetadata | Map<string, Metadata>-
systemsystem infoMetadata | Map<string, Metadata>-

MetaData

PropDescriptionTypeDefault
nameNamestring-
avatarAvatarstring-
colorAvatar 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, yellowAvatarColorgrey

Message

PropDescriptionTypeDefault
roleRolestring-
nameNamestring-
idUnique IDstring-
contentMessage contentstring| ContentItem[]-
createdAtCreated at (timestamp)number-
modelModel namestring-
statusMessage status: queued | in_progress | incomplete | completed | failed | cancelledstringcompleted
editingWhether the message is being edited, written by the componentboolean-
likeWhether the message is liked, written by the componentboolean-
dislikeWhether the message is disliked, written by the componentboolean-
referencesFiles or text cited by the messageReference[]-
errorStreaming 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.

PropDescriptionTypeDefault
idUnique identifier, requiredstring-
typeType, requiredstring-
nameNamestring-
urlURLstring-
contentText contentstring-

Methods

The methods are listed in the Methods table of the API Reference above. Get the root element through ref and call them:

import { AIChatDialogue, type AIChatDialogueElement } from '@fibrous/semi-ai-solid';

let dialogue: AIChatDialogueElement | undefined;
const toBottom = () => dialogue?.scrollToBottom(true);

<AIChatDialogue ref={(element) => (dialogue = element)} chats={messages()} roleConfig={roleConfig} onChatsChange={onChatsChange} />;

Static Properties

PropertyDescriptionType
defaultComponentsDefault 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 }
ReasoningReasoning block component; see AIChatDialogue.Reasoning below for its APIComponent
StepStep display component; see AIChatDialogue.Step below for its APIComponent
AnnotationReferences component; see AIChatDialogue.Annotation below for its APIComponent

To preserve default functionality while customizing code block rendering, wrap the default component in your own component and pass it through markdownRenderProps.components:

import type { ComponentProps } from 'solid-js';
import { AIChatDialogue } from '@fibrous/semi-ai-solid';

const DefaultCode = AIChatDialogue.defaultComponents.code;

function CustomCode(props: ComponentProps<typeof DefaultCode>) {
    // Add custom logic here
    return <DefaultCode {...props} />;
}

<AIChatDialogue chats={messages()} roleConfig={roleConfig} markdownRenderProps={{ components: { code: CustomCode } }} />;

AIChatDialogue.Reasoning

Props

PropertyDescriptionTypeDefault
contentReasoning content. When summary is empty, its texts joined by line breaks form the reasoning text.{ text?: string; type?: string }[]-
customRendererReplaces the rendering of the reasoning text. It receives all props of the component, and its result goes into the content area.Type(props: ReasoningWidgetProps) => JSX.Element-
markdownRenderPropsProps passed to the MarkdownRender of the reasoning text; raw comes from the reasoning text.MarkdownRenderProps-
statusReasoning status. completed shows the finished title and starts collapsed; any other value shows the thinking title and starts expanded.string-
summaryReasoning summary. When it is not empty, its texts joined by line breaks form the reasoning text.{ text?: string; type?: string }[]-

class, style, ref and native attributes land on the root div element.

AIChatDialogue.Step

Props

PropertyDescriptionTypeDefault
stepsList of steps, required. Each step has summary, status and actions; completed shows the done icon, any other status shows loading. Each action has summary, description and icon, and a step with actions expands or collapses on click, Enter or Space.Step[]-

class, style, ref and native attributes land on the root div element.

AIChatDialogue.Annotation

Props

PropertyDescriptionTypeDefault
annotationList of references; each item may have title, logo, url and detail. Required.AnnotationItemProps[] | Annotation[]-
descriptionText after the source logos; the number of references is shown when it is not set.string-
maxCountMaximum number of source logos shown; the rest are shown as +N. Required.number-
onClickCalled on click or on activation with Enter or Space, with the event and the references.Type(e: MouseEvent | KeyboardEvent, item: AnnotationItemProps[]) => void-

class, style, ref and native attributes land on the root div element.

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

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$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