Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support input render #125

Merged
merged 5 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/*"],
},
},
}
Loading