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

[WIP] Feat/add proInputArea #255

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
444 changes: 444 additions & 0 deletions src/ProChat/__test__/__snapshots__/demo.test.tsx.snap

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions src/ProChat/container/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { CSSProperties, memo, useContext, useEffect, useRef, useState } from 're
import { Flexbox } from 'react-layout-kit';

import { ChatListItemProps } from '@/ChatList/ChatListItem';
import { ProInputArea, ProInputAreaProps } from '@ant-design/pro-chat';
import { ConfigProvider } from 'antd';
import ChatList from '../components/ChatList';
import ChatInputArea, { ChatInputAreaProps } from '../components/InputArea';
import ChatScrollAnchor from '../components/ScrollAnchor';
import useProChatLocale from '../hooks/useProChatLocale';
import { useStore } from '../store';
import { useOverrideStyles } from './OverrideStyle';
import { ProChatChatReference } from './StoreUpdater';
import { ProChatProps } from './index';
Expand Down Expand Up @@ -52,21 +53,21 @@ export interface ConversationProps extends ProChatProps<any> {
* @param onClearAllHistory 清除所有历史记录的回调函数
* @returns 渲染的 React 元素
*/
inputAreaRender?: ChatInputAreaProps['inputAreaRender'];
inputAreaRender?: ProInputAreaProps['inputAreaRender'];
/**
* 输入框的渲染函数
* @param defaultDom 默认的 DOM 元素
* @param onMessageSend 发送消息的回调函数
* @param props 输入框的属性
*/
inputRender: ChatInputAreaProps['inputRender'];
inputRender: ProInputAreaProps['inputRender'];

/**
* 聊天发送按钮的渲染配置
* @param defaultDom 默认的 DOM 元素
* @param defaultProps 默认的属性
*/
sendButtonRender?: ChatInputAreaProps['sendButtonRender'];
sendButtonRender?: ProInputAreaProps['sendButtonRender'];

/**
* 滚动时候的监听方法
Expand Down Expand Up @@ -101,6 +102,7 @@ const App = memo<ConversationProps>(
const [height, setHeight] = useState('100%' as string | number);
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const { localeObject } = useProChatLocale();
const [sendMessage, clearMessage] = useStore((s) => [s.sendMessage, s.clearMessage]);

useEffect(() => {
// 保证 ref 永远存在
Expand Down Expand Up @@ -170,10 +172,17 @@ const App = memo<ConversationProps>(
{renderInputArea !== null && inputAreaRender !== null && (
<div ref={areaHtml}>
{
<ChatInputArea
<ProInputArea
sendButtonRender={sendButtonRender}
inputAreaRender={inputAreaRender || renderInputArea}
inputRender={inputRender}
sendMessage={sendMessage}
sendShortcutKey="enter"
clearMessage={clearMessage}
stopGenerateMessage={function (): void {
throw new Error('Function not implemented.');
}}
isLoading={false}
/>
}
</div>
Expand Down
184 changes: 184 additions & 0 deletions src/ProChat/container/AppNew.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import BackBottom from '@/BackBottom';
import { createStyles } from 'antd-style';
import RcResizeObserver from 'rc-resize-observer';
import { CSSProperties, memo, useContext, useEffect, useRef, useState } from 'react';
import { Flexbox } from 'react-layout-kit';

import { ChatListItemProps } from '@/ChatList/ChatListItem';
import { ConfigProvider } from 'antd';
import ProInputArea, { ProInputAreaProps } from '../../ProInputArea';
import ChatList from '../components/ChatList';
import ChatScrollAnchor from '../components/ScrollAnchor';
import useProChatLocale from '../hooks/useProChatLocale';
import { useOverrideStyles } from './OverrideStyle';
import { ProChatChatReference } from './StoreUpdater';
import { ProChatProps } from './index';

const useStyles = createStyles(
({ css, responsive, stylish }) => css`
overflow: hidden scroll;
height: 100%;
${responsive.mobile} {
${stylish.noScrollbar}
width: 100%;
}
`,
);

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

/**
* 聊天发送按钮的渲染配置
* @param defaultDom 默认的 DOM 元素
* @param defaultProps 默认的属性
*/
sendButtonRender?: ProInputAreaProps['sendButtonRender'];

/**
* 滚动时候的监听方法
*/
onScroll?: (e: Event) => void;

renderErrorMessages?: ChatListItemProps['renderErrorMessages'];
}

const App = memo<ConversationProps>(
({
renderInputArea,
inputAreaRender,
className,
style,
showTitle,
chatRef,
itemShouldUpdate,
inputRender,
chatItemRenderConfig,
backToBottomConfig,
renderErrorMessages,
sendButtonRender,
onScroll,
markdownProps,
}) => {
const ref = useRef<HTMLDivElement>(null);
const areaHtml = useRef<HTMLDivElement>(null);
const { styles, cx } = useStyles();
const { styles: override } = useOverrideStyles();
const [isRender, setIsRender] = useState(false);
const [height, setHeight] = useState('100%' as string | number);
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const { localeObject } = useProChatLocale();

useEffect(() => {
// 保证 ref 永远存在
setIsRender(true);
if (chatRef?.current) {
chatRef.current.scrollToBottom = () => {
(ref as any)?.current?.scrollTo({
behavior: 'smooth',
left: 0,
top: ref.current?.scrollHeight || 99999,
});
};
}
}, []);

const prefixClass = getPrefixCls('pro-chat');
return (
<RcResizeObserver
onResize={(e) => {
if (e.height !== height) {
setHeight(e.height);
}
}}
>
<Flexbox
className={cx(override.container, className, `${prefixClass}-container`)}
style={{
maxHeight: '100vh',
height: '100%',
...style,
}}
>
<>
<div
ref={ref}
className={cx(`${prefixClass}-chat-list-container`, styles)}
style={{
height: (height as number) - (areaHtml.current?.clientHeight || 0) || '100%',
}}
>
<ChatList
showTitle={showTitle}
itemShouldUpdate={itemShouldUpdate}
chatItemRenderConfig={chatItemRenderConfig}
markdownProps={markdownProps}
renderErrorMessages={renderErrorMessages}
/>
{ref?.current && <ChatScrollAnchor target={ref} />}
</div>
{isRender && ref?.current ? (
<BackBottom
style={{
bottom: 138,
}}
onScroll={onScroll}
target={ref}
text={localeObject.backToBottom}
{...backToBottomConfig}
/>
) : null}
</>
{renderInputArea !== null && inputAreaRender !== null && (
<div ref={areaHtml}>
{
<ProInputArea

Check failure on line 168 in src/ProChat/container/AppNew.tsx

View workflow job for this annotation

GitHub Actions / test

Property 'isLoading' is missing in type '{ sendButtonRender: (defaultDom: ReactNode, defaultProps: ButtonProps) => ReactNode; inputAreaRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<...>, onClearAllHistory: () => void) => ReactNode; inputRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<....' but required in type 'ProInputAreaProps'.

Check failure on line 168 in src/ProChat/container/AppNew.tsx

View workflow job for this annotation

GitHub Actions / test

Property 'isLoading' is missing in type '{ sendButtonRender: (defaultDom: ReactNode, defaultProps: ButtonProps) => ReactNode; inputAreaRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<...>, onClearAllHistory: () => void) => ReactNode; inputRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<....' but required in type 'ProInputAreaProps'.
sendButtonRender={sendButtonRender}
inputAreaRender={inputAreaRender || renderInputArea}
inputRender={inputRender}
sendShortcutKey="enter"
extra={['image', 'audio']}
/>
}
</div>
)}
</Flexbox>
</RcResizeObserver>
);
},
);

export default App;
62 changes: 62 additions & 0 deletions src/ProInputArea/components/ControlPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ActionIcon from '@/ActionIcon';
import useProChatLocale from '@/ProChat/hooks/useProChatLocale';
import { ConfigProvider, Popconfirm } from 'antd';
import { createStyles, cx } from 'antd-style';
import { Trash2 } from 'lucide-react';
import { Flexbox } from 'react-layout-kit';
// import { useStore } from '../ProChat/store';

const useStyles = createStyles(({ css, token }) => ({
extra: css`
color: ${token.colorTextTertiary};
`,
}));

interface ControlPanelProps {
className?: string;
clearMessage?: () => void;
actionsRender?: (defaultDoms: React.ReactNode[]) => React.ReactNode;
flexConfig?: Record<string, any>;
}

export const ActionBar = ({
className,
clearMessage,
actionsRender,
flexConfig,
}: ControlPanelProps) => {
const { localeObject } = useProChatLocale();

const { styles, theme } = useStyles();
const defaultDoms = [
<Popconfirm
title={localeObject.clearModalTitle}
okButtonProps={{ danger: true }}
okText={localeObject.clearDialogue}
cancelText={localeObject.cancel}
key={'clear'}
onConfirm={() => {
clearMessage();
}}
>
<ActionIcon title={localeObject.clearCurrentDialogue} icon={Trash2} />
</Popconfirm>,
];

return (
<ConfigProvider theme={{ token: { colorText: theme.colorTextSecondary } }}>
<Flexbox
align={'center'}
direction={'horizontal-reverse'}
paddingInline={12}
className={cx(styles.extra, className)}
gap={8}
{...flexConfig}
>
{actionsRender?.(defaultDoms) ?? defaultDoms}
</Flexbox>
</ConfigProvider>
);
};

export default ActionBar;
Loading
Loading