Skip to content

Commit

Permalink
feat: support input render & change api as inputAreaRender (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenshuai2144 authored Mar 14, 2024
1 parent b25b222 commit b6aaaf7
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 58 deletions.
8 changes: 4 additions & 4 deletions docs/guide/demos/renderInputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { PlusOutlined } from '@ant-design/icons';
import { ProChat } from '@ant-design/pro-chat';
import { Button, Form, Input, Space, Upload, message } from 'antd';
import { useTheme } from 'antd-style';
import { ReactNode } from 'react';
import React from 'react';

export default () => {
const theme = useTheme();

const renderInputArea = (
_: ReactNode,
const inputAreaRender = (
_: React.ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClear: () => void,
) => {
Expand Down Expand Up @@ -76,7 +76,7 @@ export default () => {

return (
<div style={{ background: theme.colorBgLayout, height: '100vh' }}>
<ProChat renderInputArea={renderInputArea} />
<ProChat inputAreaRender={inputAreaRender} />
</div>
);
};
6 changes: 3 additions & 3 deletions docs/guide/multimodal.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ nav:

## 自定义输入部分

我们提供了一个 renderInputArea 的 api,来帮助你对多模态的情况下进行支持,以及和 ProChat 的数据流进行接入和交互
我们提供了一个 inputAreaRender 的 api,来帮助你对多模态的情况下进行支持,以及和 ProChat 的数据流进行接入和交互

```ts
renderInputArea?: (
inputAreaRender?: (
defaultDom: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClearAllHistory: () => void,
) => ReactNode;
```

renderInputArea 共有三个参数:
inputAreaRender 共有三个参数:

- defaultDom :即默认渲染的 dom,你如果是想包裹或者添加一些小内容,可以直接在这个基础上进行组合
- onMessageSend :发送数据的方法,这个方法和 ProChat.sendMessage(Hooks) 本质上是一个方法,用于向 ProChat 的数据流发送一条数据
Expand Down
71 changes: 44 additions & 27 deletions src/ProChat/components/InputArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,22 @@ const useStyles = createStyles(({ css, responsive, token }) => ({
`,
}));

type ChatInputAreaProps = {
export type ChatInputAreaProps = {
className?: string;
onSend?: (message: string) => boolean | Promise<boolean>;
renderInputArea?: (
inputRender?: (
defaultDom: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
) => ReactNode;
inputAreaRender?: (
defaultDom: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClearAllHistory: () => void,
) => ReactNode;
};

export const ChatInputArea = (props: ChatInputAreaProps) => {
const { className, onSend, renderInputArea } = props || {};
const { className, onSend, inputAreaRender, inputRender } = props || {};
const [sendMessage, isLoading, placeholder, inputAreaProps, clearMessage, stopGenerateMessage] =
useStore((s) => [
s.sendMessage,
Expand Down Expand Up @@ -93,6 +97,40 @@ export const ChatInputArea = (props: ChatInputAreaProps) => {

const prefixClass = getPrefixCls('pro-chat-input-area');

const defaultInput = (
<AutoCompleteTextArea
placeholder={placeholder || '请输入内容...'}
{...inputAreaProps}
className={cx(styles.input, inputAreaProps?.className, `${prefixClass}-component`)}
value={message}
onChange={(e) => {
setMessage(e.target.value);
}}
autoSize={{ maxRows: 8 }}
onCompositionStart={() => {
isChineseInput.current = true;
}}
onCompositionEnd={() => {
isChineseInput.current = false;
}}
onPressEnter={(e) => {
if (!isLoading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
send();
}
}}
/>
);

/**
* 支持下自定义输入框
*/
const inputDom = inputRender
? inputRender?.(defaultInput, (message) => {
sendMessage(message);
})
: defaultInput;

const defaultInputArea = (
<ConfigProvider
theme={{
Expand All @@ -113,28 +151,7 @@ export const ChatInputArea = (props: ChatInputAreaProps) => {
align={'center'}
className={cx(styles.boxShadow, `${prefixClass}-text-container`)}
>
<AutoCompleteTextArea
placeholder={placeholder || '请输入内容...'}
{...inputAreaProps}
className={cx(styles.input, inputAreaProps?.className, `${prefixClass}-component`)}
value={message}
onChange={(e) => {
setMessage(e.target.value);
}}
autoSize={{ maxRows: 8 }}
onCompositionStart={() => {
isChineseInput.current = true;
}}
onCompositionEnd={() => {
isChineseInput.current = false;
}}
onPressEnter={(e) => {
if (!isLoading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
send();
}
}}
/>
{inputDom}
{isLoading ? (
<Button
type="text"
Expand All @@ -155,8 +172,8 @@ export const ChatInputArea = (props: ChatInputAreaProps) => {
</ConfigProvider>
);

if (renderInputArea) {
return renderInputArea(
if (inputAreaRender) {
return inputAreaRender(
defaultInputArea,
(message) => {
sendMessage(message);
Expand Down
53 changes: 43 additions & 10 deletions src/ProChat/container/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import BackBottom from '@/BackBottom';
import { createStyles } from 'antd-style';
import RcResizeObserver from 'rc-resize-observer';
import { CSSProperties, ReactNode, memo, useContext, useEffect, useRef, useState } from 'react';
import { CSSProperties, memo, useContext, useEffect, useRef, useState } from 'react';
import { Flexbox } from 'react-layout-kit';

import { ConfigProvider } from 'antd';
import ChatList from '../components/ChatList';
import ChatInputArea from '../components/InputArea';
import ChatInputArea, { ChatInputAreaProps } from '../components/InputArea';
import ChatScrollAnchor from '../components/ScrollAnchor';
import { useOverrideStyles } from './OverrideStyle';
import { ProChatChatReference } from './StoreUpdater';
Expand All @@ -23,26 +23,52 @@ const useStyles = createStyles(
`,
);

interface ConversationProps extends ProChatProps<any> {
/**
* 对话组件的属性接口
*/
export interface ConversationProps extends ProChatProps<any> {
/**
* 是否显示标题
*/
showTitle?: boolean;
/**
* 样式对象
*/
style?: CSSProperties;
/**
* CSS类名
*/
className?: string;
/**
* 聊天引用
*/
chatRef?: ProChatChatReference;
renderInputArea?: (
defaultDom: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClearAllHistory: () => void,
) => ReactNode;
/**
* 输入区域的渲染函数
* @param defaultDom 默认的 DOM 元素
* @param onMessageSend 发送消息的回调函数
* @param onClearAllHistory 清除所有历史记录的回调函数
* @returns 渲染的 React 元素
*/
inputAreaRender?: ChatInputAreaProps['inputAreaRender'];
/**
* 输入框的渲染函数
* @param defaultDom 默认的 DOM 元素
* @param onMessageSend 发送消息的回调函数
*/
inputRender: ChatInputAreaProps['inputRender'];
}

const App = memo<ConversationProps>(
({
renderInputArea,
inputAreaRender,
className,
style,
showTitle,
chatRef,
itemShouldUpdate,
inputRender,
chatItemRenderConfig,
backToBottomConfig,
markdownProps,
Expand Down Expand Up @@ -112,8 +138,15 @@ const App = memo<ConversationProps>(
/>
) : null}
</>
{renderInputArea !== null && (
<div ref={areaHtml}>{<ChatInputArea renderInputArea={renderInputArea} />}</div>
{renderInputArea !== null && inputAreaRender !== null && (
<div ref={areaHtml}>
{
<ChatInputArea
inputAreaRender={inputAreaRender || renderInputArea}
inputRender={inputRender}
/>
}
</div>
)}
</Flexbox>
</RcResizeObserver>
Expand Down
65 changes: 57 additions & 8 deletions src/ProChat/container/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,72 @@
import { App as Container } from 'antd';
import { CSSProperties, ReactNode } from 'react';
import { CSSProperties } from 'react';

import App from './App';
import App, { ConversationProps } from './App';

import { DevtoolsOptions } from 'zustand/middleware';
import { BackBottomProps } from '../../BackBottom';
import { ChatProps } from '../store';
import { ProChatProvider } from './Provider';
import { ProChatChatReference } from './StoreUpdater';

/**
* ProChatProps 是 ProChat 组件的属性类型定义。
* @template T - 聊天记录的数据类型
*/
export interface ProChatProps<T extends Record<string, any>> extends ChatProps<T> {
renderInputArea?: (
defaultDom: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClearAllHistory: () => void,
) => ReactNode;
/**
* @deprecated 请使用 inputAreaRender 属性替代此属性
*/
renderInputArea?: ConversationProps['inputAreaRender'];

/**
* inputAreaRender 是一个函数,用于自定义输入区域的渲染。
* @param defaultDom 默认的 DOM 元素
* @param onMessageSend 发送消息的回调函数
* @param onClearAllHistory 清除所有历史记录的回调函数
*/
inputAreaRender?: ConversationProps['inputAreaRender'];

/**
* inputRender 是一个函数,用于自定义输入框的渲染。
* @param defaultDom 默认的 DOM 元素
* @param onMessageSend 发送消息的回调函数
*/
inputRender?: ConversationProps['inputRender'];

/**
* __PRO_CHAT_STORE_DEVTOOLS__ 是一个可选的布尔值或 DevtoolsOptions 对象,用于开启 ProChat 的开发者工具。
*/
__PRO_CHAT_STORE_DEVTOOLS__?: boolean | DevtoolsOptions;

/**
* showTitle 是一个可选的布尔值,用于控制是否显示聊天窗口的标题。
*/
showTitle?: boolean;

/**
* style 是一个可选的 CSSProperties 对象,用于自定义聊天窗口的样式。
*/
style?: CSSProperties;

/**
* className 是一个可选的字符串,用于自定义聊天窗口的类名。
*/
className?: string;

/**
* chatRef 是一个可选的 ProChatChatReference 对象,用于获取 ProChat 组件的引用。
*/
chatRef?: ProChatChatReference;

/**
* appStyle 是一个可选的 CSSProperties 对象,用于自定义整个应用的样式。
*/
appStyle?: CSSProperties;

/**
* backToBottomConfig 是一个 Omit<BackBottomProps, 'target'> 对象,用于配置返回底部按钮的行为。
*/
backToBottomConfig?: Omit<BackBottomProps, 'target'>;
}

Expand All @@ -33,7 +79,9 @@ export function ProChat<T extends Record<string, any> = Record<string, any>>({
chatItemRenderConfig,
backToBottomConfig,
appStyle,
inputRender,
markdownProps,
inputAreaRender,
...props
}: ProChatProps<T>) {
return (
Expand All @@ -49,7 +97,8 @@ export function ProChat<T extends Record<string, any> = Record<string, any>>({
>
<App
chatItemRenderConfig={chatItemRenderConfig}
renderInputArea={renderInputArea}
inputRender={inputRender}
inputAreaRender={renderInputArea || inputAreaRender}
chatRef={props.chatRef}
showTitle={showTitle}
style={style}
Expand Down
4 changes: 2 additions & 2 deletions src/ProChat/demos/renderInputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default () => {

const [isRender, setIsRender] = useState(true);

const renderInputArea = (
const inputAreaRender = (
_: ReactNode,
onMessageSend: (message: string) => void | Promise<any>,
onClear: () => void,
Expand Down Expand Up @@ -81,7 +81,7 @@ export default () => {
<Button type="primary" onClick={() => setIsRender(!isRender)}>
切换是否渲染输入框
</Button>
<ProChat renderInputArea={isRender ? renderInputArea : () => null} />
<ProChat inputAreaRender={isRender ? inputAreaRender : () => null} />
</div>
);
};
2 changes: 1 addition & 1 deletion src/ProChat/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ ProChat 使用 `meta` 来表意会话双方的头像、名称等信息。设定

## 自定义输入区域

有些时候会觉得默认的输入区域不够好用,或是你有一些输入模块的自定义需求,可以使用 renderInputArea 来进行自定义输入,如果不需要输入区域可以传入 `renderInputArea={()=>null}`
有些时候会觉得默认的输入区域不够好用,或是你有一些输入模块的自定义需求,可以使用 inputAreaRender 来进行自定义输入,如果不需要输入区域可以传入 `inputAreaRender={()=>null}`

下面是一个支持图片上传的示范案例,试试上传文件并提交看看吧。

Expand Down
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@@/*": [".dumi/tmp/*"],
"@/*": ["src/*"],
"@ant-design/pro-chat": ["src"],
"@ant-design/pro-chat/*": ["src/*"]
}
}
"@ant-design/pro-chat/*": ["src/*"],
},
},
}

0 comments on commit b6aaaf7

Please sign in to comment.