UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

1,375 lines (1,210 loc) 68.9 kB
--- localeCode: en-US order: 102 category: Plus title: AIChatDialogue icon: doc-aiDialogue width: 60% brief: Display AI chat conversation messages to users showNew: true --- ## 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](https://platform.openai.com/docs/api-reference/responses/object), and supports the OpenAI community's [Response](https://platform.openai.com/docs/api-reference/responses/create) / [Chat Completion](https://platform.openai.com/docs/api-reference/chat/create) format standards by default. Responses to GPT-5 and GPT-4o series models are supported out of the box. For details, see [Message Data Conversion](/zh-CN/ai/aiChatDialogue#%E6%B6%88%E6%81%AF%E6%95%B0%E6%8D%AE%E8%BD%AC%E6%8D%A2). ## Demos ### How to import ```jsx import import { AIChatDialogue } from '@douyinfe/semi-ui'; ``` ### Basic Usage Set `chats` and `onChatsChange` to enable basic conversation display and interactions. Use `align` to control layout: `leftRight` (default) and `leftAlign`. ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue, RadioGroup, Radio } from '@douyinfe/semi-ui'; const defaultMessages = [ { role: 'system', id: '1', createAt: 1715676751919, content: "Hello, I'm your AI assistant.", }, { role: 'user', id: '2', createAt: 1715676751919, content: "Give an example of using the Semi Design Button component", }, { role: 'assistant', id: '3', createAt: 1715676751919, content: "Here is a sample usage of a Semi component:\n```jsx \nimport React from 'react';\nimport { Button } from '@douyinfe/semi-ui';\n\nconst MyComponent = () => {\n return (\n <Button>Click me</Button>\n );\n};\nexport default MyComponent;\n```\n", } ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function AlignAndMode () { const [messages, setMessage] = useState(defaultMessages); const [mode, setMode] = useState('bubble'); const [align, setAlign] = useState('leftRight'); const onAlignChange = useCallback((e) => { setAlign(e.target.value); }, []); const onModeChange = useCallback((e) => { setMode(e.target.value); }, []); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); return ( <> <span style={{ display: 'flex', flexDirection: 'column', rowGap: '8px' }}> <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}> Mode <RadioGroup onChange={onModeChange} value={mode} type={"button"}> <Radio value={'bubble'}>Bubble</Radio> <Radio value={'noBubble'}>No bubble</Radio> <Radio value={'userBubble'}>User bubble</Radio> </RadioGroup> </span> <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}> Conversation layout <RadioGroup onChange={onAlignChange} value={align} type={"button"}> <Radio value={'leftRight'}>LeftRight</Radio> <Radio value={'leftAlign'}>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> </> ); } render(AlignAndMode); ``` ### 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](#Message) for details. The `status` follows the same values as the [Response API Status](https://platform.openai.com/docs/api-reference/responses/object#responses/object-status): 6 status values mapping to 3 official styles (success / in progress / failed). ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; const defaultMessages = [ { role: 'assistant', id: '1', createAt: 1715676751919, content: "Success", // default status is completed }, { id: 'loading', role: 'assistant', status: 'in_progress' // Same visual as queued, incomplete }, { role: 'assistant', id: 'error', content: 'Error', status: 'failed' // Same visual as cancelled } ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function StatusDemo () { const [messages, setMessage] = useState(defaultMessages); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); return ( <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} /> ); } render(StatusDemo); ``` ### Message Display The message content uses [ContentItem[]](https://platform.openai.com/docs/api-reference/responses/list#responses/list-data). 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). ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; import { IconSearchStroked, IconCodeStroked, IconBriefStroked } from '@douyinfe/semi-icons'; const defaultMessages = [ { role: 'assistant', id: '1', createAt: 1715676751919, content: 'Plain text', }, { id: '2', role: 'user', content: [ { type: 'message', content: [ { type: 'input_text', text: 'Help me generate a similar image', }, { type: 'input_image', image_url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/edit-bag.jpeg', file_id: 'demo-file-id' }, { type: 'input_text', text: 'Files preview below', }, { 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': '\nI need to reason and answer the user about what the Semi component library is...' } ], }, { type: 'message', content: [ { type: 'output_text', text: 'Semi Design is a design system built and maintained by ByteDance Frontend Team and the MED Product Design Team.' } ], 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: 'Congrats! You now know everything about Semi Design!', annotations: [ { title: 'semi.design', url: 'https://semi.design/', detail: 'semi design page', logo: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, { title: 'semi.design', url: 'https://semi.design/', detail: 'semi design page', logo: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, ] } ] }, { type: 'plan', content: [ { summary: 'Create a comprehensive Beijing travel guide covering attractions, lodging, transport, food, and tips', steps: [ { summary: 'Search introductions and ticket info for Beijing attractions', description: 'Searching: Beijing attraction introductions and ticket info', type: 'search', }, { summary: 'Read specific lines of a given file', description: 'Creating document: Beijing Travel Guide', type: 'docs', }, { summary: 'Create a file containing the Beijing travel guide', description: 'Creating code file: beijing_travel_guide.html', type: 'code', }, ], statues: 'completed' }, { summary: 'Summarize the created Beijing travel guide and present to the user', steps: [] } ], } ], status: 'completed', }, ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function AllTypeMessageDemo () { const [messages, setMessage] = useState(defaultMessages); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); const mapStep = useCallback((steps) => { 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(() => ({ 'plan': (item) => { // 'plan' is a custom user-defined type let steps = item.content.map((item) => { 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} /> ); } render(AllTypeMessageDemo); ``` ### 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. ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; const defaultMessages = [ { id: '1', role: 'user', content: 'This message is an example for the References demo', references: [ { id: '1', type: 'text', content: 'Sample text. This is a long paragraph of text repeated for demonstration purposes to show truncation and layout behavior in the reference area.', }, { id: '2', name: 'Feishu Doc.docx', }, { id: '3', name: 'Music.mp4', }, { id: '4', name: 'Image.jpeg', url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/Resso.png' }, { id: '5', name: 'code.json', } ] } ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function ReferencesDemo () { const [messages, setMessage] = useState(defaultMessages); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); const onReferenceClick = () => { console.log('You click the reference button!'); }; return ( <AIChatDialogue chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} showReference onReferenceClick={onReferenceClick} /> ); } render(ReferencesDemo); ``` ### Selection ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue, RadioGroup, Radio } from '@douyinfe/semi-ui'; const defaultMessages = [ { role: 'system', id: '1', createAt: 1715676751919, content: "Hello, I'm your AI assistant.", }, { role: 'user', id: '2', createAt: 1715676751919, content: "Give an example of using the Semi Design Button component", }, { role: 'assistant', id: '3', createAt: 1715676751919, content: "Here is a sample usage of a Semi component:\n```jsx \nimport React from 'react';\nimport { Button } from '@douyinfe/semi-ui';\n\nconst MyComponent = () => {\n return (\n <Button>Click me</Button>\n );\n};\nexport default MyComponent;\n```\n", } ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function SelectingDemo () { const ref = useRef(null); const [messages, setMessage] = useState(defaultMessages); const [align, setAlign] = useState('leftRight'); const [select, setSelect] = useState(true); const [selection, setSelection] = useState('allSelect'); useEffect(() => { ref.current.selectAll(); }, []); const onSelectChange = useCallback((e) => { setSelect(e.target.value); }, []); const onSelectionChange = useCallback((e) => { if (e.target.value === 'allSelect') { ref.current.selectAll(); } else { ref.current.deselectAll(); } setSelection(e.target.value); }, []); const onSelect = useCallback((selectionId) => { console.log('onSelect', selectionId); }, []); const onAlignChange = useCallback((e) => { setAlign(e.target.value); }, []); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); return ( <div> <span style={{ display: 'flex', flexDirection: 'column', rowGap: '8px' }}> <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}> Session Layout <RadioGroup onChange={onAlignChange} value={align} type={"button"}> <Radio value={'leftRight'}>Left Right</Radio> <Radio value={'leftAlign'}>Left Align</Radio> </RadioGroup> </span> <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}> Whether to Enable Selection <RadioGroup onChange={onSelectChange} value={select} type={"button"}> <Radio value={true}>ON</Radio> <Radio value={false}>OFF</Radio> </RadioGroup> </span> <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px' }}> Selection Method <RadioGroup onChange={onSelectionChange} value={selection} type={"button"}> <Radio value={'allSelect'}>AllSelect</Radio> <Radio value={'cancelSelect'}>CancelAllSelect</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> ); } render(SelectingDemo); ``` <!-- todo --> <!-- ### 编辑消息 --> <!-- ```jsx live=true dir="column" noInline=true ``` --> ### Hints Use `hints` to set the hint area content. Clicking a hint populates it as the new user input and triggers `onHintClick`. ```jsx live=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; () => { const defaultMessages = [ { role: 'assistant', id: '1', createAt: 1715676751919, content: 'Semi Design is a design system designed, developed, and maintained by the TikTok front-end team and the MED product design team. You can ask me any questions about Semi.', } ]; const hintsExample = [ "What are the commonly used components in the Semi component library?", "Can you show an example of a page built using the Semi component library?", "Is there any official documentation for the Semi component library?", ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; const [messages, setMessage] = useState(defaultMessages); const [hints, setHints] = useState(hintsExample); const onChatsChange = useCallback((chats) => { console.log('onChatsChange', chats); setMessage(chats); }, []); const onHintClick = useCallback((hint) => { 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: ```ts type renderHintBox = (props: {content: string; index: number,onHintClick: () => void}) => React.ReactNode; ``` ```jsx live=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; import { IconArrowRight } from '@douyinfe/semi-icons'; () => { const defaultMessages = [ { role: 'assistant', id: '1', createAt: 1715676751919, content: 'Semi Design is a design system created and maintained by ByteDance Frontend Team and the MED Product Design Team. You can ask me anything about Semi.', } ]; const hintsExample = [ "What are the commonly used components in the Semi component library?", "Can you show an example of a page built using the Semi component library?", "Is there any official documentation for the Semi component library?", ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; const [messages, setMessage] = useState(defaultMessages); const [hints, setHints] = useState(hintsExample); const onChatsChange = useCallback((chats) => { console.log('onChatsChange', chats); setMessage(chats); }, []); const onHintClick = useCallback((hint) => { setHints([]); }, []); const commonHintStyle = useMemo(() => ({ border: '1px solid var(--semi-color-border)', padding: '10px', borderRadius: '10px', color: 'var( --semi-color-text-1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer', fontSize: '14px' }), []); const renderHintBox = useCallback((props) => { const { content, onHintClick, index } = props; return ( <div style={commonHintStyle} onClick={onHintClick} key={index}> {content} <IconArrowRight style={{ marginLeft: 10 }}>click me</IconArrowRight> </div> ); }, []); return ( <AIChatDialogue align="leftRight" mode="bubble" chats={messages} roleConfig={roleConfig} onChatsChange={onChatsChange} hints={hints} onHintClick={onHintClick} renderHintBox={renderHintBox} /> ); }; ``` ### Custom Chat Box Rendering Pass custom rendering configuration via `chatBoxRenderConfig`. Types: ```ts 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 | Map<string, Metadata>; defaultContent?: ReactNode | ReactNode[]; className?: string; } export interface DefaultActionNodeObj { copyNode: ReactNode; likeNode: ReactNode; dislikeNode: ReactNode; resetNode: 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 { /* Customize message title rendering */ renderDialogueAction?: (props: RenderActionProps) => ReactNode; /* Customize avatar rendering */ renderDialogueAvatar?: (props: RenderAvatarProps) => ReactNode; /* Customize content rendering */ renderDialogueContent?: (props: RenderContentProps) => ReactNode; /* Customize action bar rendering */ renderDialogueTitle?: (props: RenderTitleProps) => ReactNode; /* Fully customize the entire chat box */ renderFullDialogue?: (props: RenderFullDialogueProps) => ReactNode } ``` You can customize the avatar and title via `renderChatBoxAvatar` and `renderChatBoxTitle`. ```jsx live=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue, Avatar } from '@douyinfe/semi-ui'; () => { const defaultMessages = [ { role: 'system', id: '1', createAt: 1715676751919, content: "Hello, I'm your AI assistant.", }, { role: 'user', id: '2', createAt: 1715676751919, content: "Give an example of using the Semi Design Button component", }, { role: 'assistant', id: '3', createAt: 1715676751919, content: "Here is a sample usage of a Semi component:\n```jsx \nimport React from 'react';\nimport { Button } from '@douyinfe/semi-ui';\n\nconst MyComponent = () => {\n return (\n <Button>Click me</Button>\n );\n};\nexport default MyComponent;\n```\n", } ]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; const [messages, setMessage] = useState(defaultMessages); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); const renderConfig = { renderDialogueTitle: (props) => { return <div className="semi-ai-chat-dialogue-title">My-{props.role.name}</div>; }, renderDialogueAvatar: (props) => { return <Avatar src={props.role.avatar} size="extra-small" shape="square" > </Avatar>; }, renderDialogueAction: (props) => { return <div className={props.className}>{props.defaultActions[0]}</div>; }, }; 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. Example: ```jsx live=true dir="column" noInline=true import React, { useState, useCallback } from 'react'; import { AIChatDialogue, MarkdownRender } from '@douyinfe/semi-ui'; const defaultMessages = [ { id: '1', role: 'user', content: 'Hello', }, { id: '2', role: 'assistant', content: 'Hello! How can I help you today?', status: 'completed', }, { id: '3', role: 'user', content: [ { type: 'message', role: 'user', content: [ { type: 'input_text', text: 'Help me generate a similar image', }, { type: 'input_image', image_url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/edit-bag.jpeg', file_id: 'demo-file-id' }, { type: 'input_image', image_url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/edit-bag.jpeg', file_id: 'demo-file-id' } ], }], }, { id: '4', role: 'assistant', content: [{ type: "reasoning", summary: [ { "type": "summary_text", "text": "\nThe user asked me to generate a similar image. I need to analyze the image first, then generate a similar one..." } ], annotations: [ { title: 'semi.design', url: 'https://semi.design/', detail: 'semi design page', logo: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, { title: 'semi.design', url: 'https://semi.design/', detail: 'semi design page', logo: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, ], status: "completed" }, { type: 'function_call', name: 'create_travel_guide', arguments: "{\n\"city\": \"Beijing\"\n}", status: 'completed', } ], status: 'completed', }]; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function CustomRender () { const [messages, setMessage] = useState(defaultMessages); const onChatsChange = useCallback((chats) => { 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) => { return <React.Fragment> <AIChatDialogue.Annotation annotation={props.annotations} description={'References'} maxCount={3} onClick={(e) => { 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 = { "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} /> ); } render(CustomRender); ``` ### Message Data Adapters The current component's dialogue messages are modeled after OpenAI's [Response Object](https://platform.openai.com/docs/api-reference/responses/object). To better support users in seamlessly integrating the [Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create) and [Response API](https://platform.openai.com/docs/api-reference/responses/create), 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`. ```ts // 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 function streamingResponseToMessage(chunks: ResponseChunk[], prevState: StreamingResponseState): { messages: Message[]; state?: StreamingResponseState } // Convert the streaming data returned by the Response API into Message format for the Chat Dialogue. function chatInputToMessage(inputContent: MessageContent): Message // 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](https://platform.openai.com/docs/api-reference/chat/create) 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.) ```jsx live=true noInline=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function ChatCompletionToMessageDemo() { const [messages, setMessage] = useState([]); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); useEffect(() => { const message = chatCompletionToMessage(CHAT_COMPLETION_DATA); setMessage([...message]); }, []); 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" } ], // ... }; render(ChatCompletionToMessageDemo); ``` For example, when a user returns streaming data using the [Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create) interface, the `streamingChatCompletionToMessage` function can be used to convert the Chat Completion Chunk Object List into a Dialogue Message format. ```jsx live=true noInline=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function StreamingChatCompletionToMessageDemo() { const [messages, setMessage] = useState([]); const [state, setState] = useState(); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); useEffect(() => { const total = STREAMING_CHAT_COMPLETION_DATA.length; let i = 1; 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); setState(nextState); const merged = [...messages, partialMessages[0]]; setMessage(merged); 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: 输出文本增量 { "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": " I'm using " }, "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: 工具调用增量(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": [{ "id": "call_1", "function": { "name": "searchWeather", "arguments": "{\"city\":\"Beijing\"}" } }] }, "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": [{ "id": "call_1", "function": { "name": null, "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": " transform to 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" } ]; render(StreamingChatCompletionToMessageDemo); ``` When a user returns non-streaming data using the [Response API](https://platform.openai.com/docs/api-reference/responses/create) interface, the `responseToMessage` function can be used to convert the Response Object into a Dialogue Message block format. ```jsx live=true noInline=true dir="column" import React, { useState, useCallback } from 'react'; import { AIChatDialogue } from '@douyinfe/semi-ui'; const roleConfig = { user: { name: 'User', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, assistant: { name: 'Assistant', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' }, system: { name: 'System', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png' } }; function ResponseToMessageDemo() { const [messages, setMessage] = useState([]); const onChatsChange = useCallback((chats) => { setMessage(chats); }, []); useEffect(() => { const responseMessage = responseToMessage(RESPONSE_DATA); setMessage([responseMessage]); }, []); 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": "**What is Semi Design?** The user asks for \"Semi Design\" which requires aggregating multiple sources. First, ByteDance's Semi Design is a design system supporting multiple platforms with Design Tokens and code conversion tools. Another result from India focuses on semiconductor training, but the user likely refers to the ByteDance one. Other results mention semi-custom design but are less relevant. We should confirm if there are other interpretations, but current info covers the main dimensions. Continuing to reason may improve completeness, but it's sufficient to answer now." } ] }, { "type": "message", "id": "msg_67ccd3acc8d48190a77525dc6de64b4104becb25c45c1d41", "status": "completed", "role": "assistant", "content": [ { "type": "output_text", "text": "Semi Design is a design system created and maintained by ByteDance's Frontend Team and the MED Product Design Team. You can ask me anything about Semi.", "annotations": [ { "title": 'Semi Design', "url": 'https://semi.design/zh-CN/start/getting-started', "detail": 'Semi Design Getting Started', "logo": 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png' }, {