Browse Source

Feat/chat markdown render (#2679)

* docs: remove repet change log

* feat: Chat add markdownRenderProps API
YyumeiZhang 10 months ago
parent
commit
b0a54ec675

+ 4 - 3
content/plus/chat/index-en-US.md

@@ -1617,8 +1617,9 @@ render(DefaultChat);
 | inputBoxStyle | Input box style | CSSProperties | - |
 | inputBoxCls | Input box className | string | - |
 | sendHotKey | Keyboard shortcut for sending content, supports `enter` \| `shift+enter`. The former will send the message in the input box when you press enter alone. When the shift and enter keys are pressed at the same time, it will only wrap the line and not send it. The latter is the opposite | string | `enter` |
+| markdownRenderProps | This parameter will be passed to the MarkdownRender component used for dialog rendering. For details, see [MarkdownRenderProps](/en-US/plus/markdownrender#API)| MarkdownRenderProps |-|
 | mode | Conversation mode, support `bubble` \| `noBubble` \| `userBubble`  | string | `bubble` |
-| roleConfig | Role information configuration, see[RoleConfig](#RoleConfig) | RoleConfig | - |
+| roleConfig | Role information configuration, see [RoleConfig](#RoleConfig) | RoleConfig | - |
 | renderDivider | Custom render divider, supported since v2.67.0 | (message?: Message) => ReactNode | - |
 | renderHintBox | Custom rendering prompt information | (props: {content: string; index: number,onHintClick: () => void}) => React.ReactNode| - |
 | onChatsChange | Triggered when the conversation list changes | (chats: Message[]) => void | - |
@@ -1637,8 +1638,8 @@ render(DefaultChat);
 | showClearContext | Whether to display the clear context button| boolean | false |
 | showStopGenerate | Whether to display the stop generation button| boolean | false |
 | topSlot | top slot for chat | React.ReactNode | - |
-| uploadProps | Upload component properties, refer to details [Upload](/zh-CN/input/upload#API%20%E5%8F%82%E8%80%83) | UploadProps | - |
-| uploadTipProps | Upload component prompt attribute, refer to details [Tooltip](/zh-CN/show/tooltip#API%20%E5%8F%82%E8%80%83) | TooltipProps | - |
+| uploadProps | Upload component properties, refer to details [Upload](/en-US/input/upload#API%20%E5%8F%82%E8%80%83) | UploadProps | - |
+| uploadTipProps | Upload component prompt attribute, refer to details [Tooltip](/en-US/show/tooltip#API%20%E5%8F%82%E8%80%83) | TooltipProps | - |
 
 
 #### RoleConfig

+ 2 - 1
content/plus/chat/index.md

@@ -1620,8 +1620,9 @@ render(DefaultChat);
 | inputBoxStyle | 输入框样式 | CSSProperties | - |
 | inputBoxCls | 输入框类名 | string | - |
 | sendHotKey | 发送输入内容的键盘快捷键,支持 `enter` \| `shift+enter`。前者在单独按下 enter 将发送输入框中的消息, shift 和 enter 按键同时按下时,仅换行,不发送。后者相反 | string | `enter` |
+| markdownRenderProps | 该参数将透传给对话框渲染所用的 MarkdownRender 组件,详见 [MarkdownRenderProps](/zh-CN/plus/markdownrender#API)| MarkdownRenderProps |-|
 | mode | 对话模式,支持 `bubble` \| `noBubble` \| `userBubble`  | string | `bubble` |
-| roleConfig | 角色信息配置,具体见[RoleConfig](#RoleConfig) | RoleConfig | - |
+| roleConfig | 角色信息配置,具体见 [RoleConfig](#RoleConfig) | RoleConfig | - |
 | renderDivider | 自定义渲染分割线, 自 v2.67.0 支持 | (message?: Message) => ReactNode | - |
 | renderHintBox | 自定义渲染提示信息 | (props: {content: string; index: number,onHintClick: () => void}) => React.ReactNode| - |
 | onChatsChange | 对话列表变化时触发 | (chats: Message[]) => void | - |

+ 0 - 1
content/start/changelog/index-en-US.md

@@ -66,7 +66,6 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 - 【Fix】
     - Fixed the problem of incorrect onChange callback result in Tree component treeDataSimpleJson mode  [#2508 ](https://github.com/DouyinFE/semi-design/issues/2508)
     - fixed the issue that the display of disabled subNavItem in vertical Navigation does not meet expectations when it is collapsed
-    - Set the max-width of the img node of the image preview to none to avoid enlargement display errors when using tailwind at the same time.[#2624](https://github.com/DouyinFE/semi-design/pull/2624)
 
 #### 🎉 2.71.2 (2024-12-13)
 - 【Fix】

+ 0 - 1
content/start/changelog/index.md

@@ -64,7 +64,6 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 - 【Fix】
   - 修复 Tree 组件 treeDataSimpleJson 模式下,onChange 回调结果错误问题  [#2508 ](https://github.com/DouyinFE/semi-design/issues/2508) [#2601](https://github.com/DouyinFE/semi-design/pull/2601)
   - 修复竖向 Navigation 在收起状态下 disabled subNavItem 展示不符合预期问题 [#2637](https://github.com/DouyinFE/semi-design/pull/2637)
-  - 设置图片预览的 img 节点的 max-width 为none,避免同时使用 tailwind 时放大显示错误问题[#2624](https://github.com/DouyinFE/semi-design/pull/2624)_
 
 
 #### 🎉 2.71.2 (2024-12-13)

+ 28 - 1
packages/semi-ui/chat/_story/chat.stories.jsx

@@ -4,7 +4,7 @@ import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react'
 import { Form, Button, Avatar, Dropdown, Radio, RadioGroup, Switch, Collapsible, AvatarGroup, Divider } from '@douyinfe/semi-ui';
 import { IconUpload, IconForward, IconMoreStroked, IconArrowRight, IconChevronUp } from '@douyinfe/semi-icons';
 import MarkdownRender from '../../markdownRender';
-import { initMessage, roleInfo, commonOuterStyle, hintsExample, infoWithAttachment, simpleInitMessage, semiCode, infoWithDivider } from './constant';
+import { initMessage, roleInfo, commonOuterStyle, hintsExample, infoWithAttachment, simpleInitMessage, semiCode, infoWithDivider, infoWithJSX } from './constant';
 
 export default {
     title: 'Chat',
@@ -900,3 +900,30 @@ export const CustomRenderDivider = () => {
         </div>
     );
 }
+
+export const MarkdownRenderProps = () => {
+    const [message, setMessage] = useState(infoWithJSX);
+    const components = {};
+    components['MyButton'] = ({ children,onClick }) => {
+        return <Button type={"primary"} onClick={onClick} style={{marginBottom:"12px"}}> {children} </Button>
+    }
+    const markdownRenderProps = {
+        format: 'mdx',
+        components: {...MarkdownRender.defaultComponents,...components}
+    }
+
+    return (
+        <div
+            style={{ height: 600}}
+        >
+            <Chat 
+                placeholder={'不处理输入信息,仅用于展示附件'}
+                style={commonOuterStyle}
+                chats={message}
+                roleConfig={roleInfo}
+                uploadProps={uploadProps}
+                markdownRenderProps={markdownRenderProps}
+            />
+        </div>
+    )
+}

+ 16 - 1
packages/semi-ui/chat/_story/constant.js

@@ -156,6 +156,20 @@ const simpleInitMessage = [
     },
 ];
 
+const infoWithJSX = [
+    {
+        role: 'system',
+        id: '1',
+        createAt: 1715676751919,
+        content: `因为用的是 mdx 模式,因此对于部分符号需要转义 \\{\\} \\<\\> ...
+#### 下面是一个渲染在 Markdown 中的按钮
+<MyButton onClick={()=>alert("点击了 MyButton")}>MyButton 点我</MyButton>
+
+直接在 Markdown 中书写 JSX 即可
+`
+    },
+]
+
 export {
     initMessage,
     roleInfo,
@@ -164,5 +178,6 @@ export {
     infoWithAttachment,
     simpleInitMessage,
     semiCode, 
-    infoWithDivider
+    infoWithDivider,
+    infoWithJSX
 };

+ 5 - 3
packages/semi-ui/chat/chatBox/chatBoxContent.tsx

@@ -1,7 +1,7 @@
 import React, { ReactElement, ReactNode, useMemo } from 'react';
 import cls from 'classnames';
 import { Message, Metadata, RenderContentProps } from '../interface';
-import MarkdownRender from '../../markdownRender';
+import MarkdownRender, { MarkdownRenderProps } from '../../markdownRender';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/chat/constants';
 import { MDXProps } from 'mdx/types';
 import { FileAttachment, ImageAttachment } from '../attachment';
@@ -16,11 +16,12 @@ interface ChatBoxContentProps {
     children?: string;
     role?: Metadata;
     message?: Message;
-    customRenderFunc?: (props: RenderContentProps) => ReactNode
+    customRenderFunc?: (props: RenderContentProps) => ReactNode;
+    markdownRenderProps?: MarkdownRenderProps;
 }
 
 const ChatBoxContent = (props: ChatBoxContentProps) => {
-    const { message = {}, customRenderFunc, role: roleInfo, customMarkDownComponents, mode } = props;
+    const { message = {}, customRenderFunc, role: roleInfo, customMarkDownComponents, mode, markdownRenderProps } = props;
     const { content, role, status } = message;
 
     const markdownComponents = useMemo(() => ({
@@ -53,6 +54,7 @@ const ChatBoxContent = (props: ChatBoxContentProps) => {
                     format='md'
                     raw={content}
                     components={markdownComponents as any}
+                    {...markdownRenderProps}
                 />;
             } else if (Array.isArray(content)) {
                 realContent = content.map((item, index) => {

+ 2 - 0
packages/semi-ui/chat/chatBox/index.tsx

@@ -22,6 +22,7 @@ const ChatBox = React.memo((props: ChatBoxProps) => {
         chatBoxRenderConfig = {}, 
         customMarkDownComponents,
         previousMessage,
+        markdownRenderProps
     } = props;
     const { renderChatBoxAvatar, renderChatBoxAction, 
         renderChatBoxContent, renderChatBoxTitle,
@@ -64,6 +65,7 @@ const ChatBox = React.memo((props: ChatBoxProps) => {
             message={message}
             customMarkDownComponents={customMarkDownComponents}
             customRenderFunc={renderChatBoxContent}
+            markdownRenderProps={markdownRenderProps}
         />);
     }, [message, info, renderChatBoxContent, mode]);
 

+ 2 - 1
packages/semi-ui/chat/chatContent.tsx

@@ -16,7 +16,7 @@ const ChatContent = React.memo((props: ChatContentProps) => {
     const { chats, onMessageBadFeedback, onMessageCopy, mode,
         onChatsChange, onMessageDelete, onMessageGoodFeedback,
         onMessageReset, roleConfig, chatBoxRenderConfig, align,
-        customMarkDownComponents, renderDivider,
+        customMarkDownComponents, renderDivider, markdownRenderProps
     } = props;
 
     const [toast, contextHolder] = Toast.useToast();
@@ -49,6 +49,7 @@ const ChatContent = React.memo((props: ChatContentProps) => {
                         lastChat={lastMessage}
                         customMarkDownComponents={customMarkDownComponents}
                         chatBoxRenderConfig={chatBoxRenderConfig}
+                        markdownRenderProps={markdownRenderProps}
                     />;
             })}
             <div className={`${PREFIX}-toast`}>{contextHolder as any}</div>

+ 3 - 1
packages/semi-ui/chat/index.tsx

@@ -65,6 +65,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
         uploadProps: PropTypes.object,
         uploadTipProps: PropTypes.object,
         mode: PropTypes.string,
+        markdownRenderProps: PropTypes.object,
     };
 
     static defaultProps = getDefaultPropsFromGlobalConfig(Chat.__SemiComponentName__, {
@@ -289,7 +290,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
             customMarkDownComponents, mode, showClearContext,
             placeholder, inputBoxCls, inputBoxStyle,
             hintStyle, hintCls, uploadProps, uploadTipProps,
-            sendHotKey, renderDivider
+            sendHotKey, renderDivider, markdownRenderProps
         } = this.props;
         const { backBottomVisible, chats, wheelScroll, uploadAreaVisible } = this.state;
         let showStopGenerateFlag = false;
@@ -347,6 +348,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
                                 onMessageCopy={onMessageCopy}
                                 chatBoxRenderConfig={chatBoxRenderConfig}
                                 renderDivider={renderDivider}
+                                markdownRenderProps={markdownRenderProps}
                             />
                             {/* hint area */}
                             {!!hints?.length && <Hint

+ 3 - 1
packages/semi-ui/chat/interface.ts

@@ -4,6 +4,7 @@ import { Upload } from '../index';
 import type { FileItem, UploadProps } from '../upload';
 import { Message } from '@douyinfe/semi-foundation/chat/foundation';
 import type { TooltipProps } from '../tooltip';
+import { MarkdownRenderProps } from '../markdownRender';
 
 export { Message };
 export interface CommonChatsProps {
@@ -19,7 +20,8 @@ export interface CommonChatsProps {
     onMessageCopy?: (message?: Message) => void;
     chatBoxRenderConfig?: ChatBoxRenderConfig;
     customMarkDownComponents?: MDXProps['components'];
-    renderDivider?: (message?: Message) => ReactNode
+    renderDivider?: (message?: Message) => ReactNode;
+    markdownRenderProps?: MarkdownRenderProps
 }
 
 export interface ChatProps extends CommonChatsProps {