Skip to content
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
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