Skip to content

Commit

Permalink
✨ feat: 更新ProInputArea组件
Browse files Browse the repository at this point in the history
  • Loading branch information
绾歌 committed Jun 26, 2024
1 parent d8488ad commit d2f5c07
Show file tree
Hide file tree
Showing 8 changed files with 1,040 additions and 0 deletions.
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

Type '{ sendButtonRender: (defaultDom: ReactNode, defaultProps: ButtonProps) => ReactNode; inputAreaRender: any; inputRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<...>, defaultProps: TextAreaProps) => ReactNode; sendShortcutKey: "enter"; extra: ("audio" | "image")[]; }' is missing the following properties from type 'ProInputAreaProps': sendMessage, stopGenerateMessage, isLoading

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

View workflow job for this annotation

GitHub Actions / test

Type '{ sendButtonRender: (defaultDom: ReactNode, defaultProps: ButtonProps) => ReactNode; inputAreaRender: any; inputRender: (defaultDom: ReactNode, onMessageSend: (message: string) => void | Promise<...>, defaultProps: TextAreaProps) => ReactNode; sendShortcutKey: "enter"; extra: ("audio" | "image")[]; }' is missing the following properties from type 'ProInputAreaProps': sendMessage, stopGenerateMessage, isLoading
sendButtonRender={sendButtonRender}
inputAreaRender={inputAreaRender || renderInputArea}
inputRender={inputRender}
sendShortcutKey="enter"
extra={['image', 'audio']}
/>
}
</div>
)}
</Flexbox>
</RcResizeObserver>
);
},
);

export default App;
64 changes: 64 additions & 0 deletions src/ProInputArea/components/ControlPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import ActionIcon from '@/ActionIcon';
import useProChatLocale from '@/ProChat/hooks/useProChatLocale';
import { ConfigProvider, Popconfirm } from 'antd';
import { createStyles, cx } from 'antd-style';
import { Globe, RotateCw, 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>,
<ActionIcon key={'regenerate'} title={localeObject.regenerate} icon={RotateCw} />,
<ActionIcon key={'connectNetwork'} title={localeObject.connectNetwork} icon={Globe} />,
];

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;
86 changes: 86 additions & 0 deletions src/ProInputArea/components/ExtraModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import ActionIcon from '@/ActionIcon';
import useProChatLocale from '@/ProChat/hooks/useProChatLocale';
import { ConfigProvider } from 'antd';
import { createStyles, cx } from 'antd-style';
import { isObject, isString } from 'lodash-es';
import { FileVideo, Image } from 'lucide-react';
import { Flexbox } from 'react-layout-kit';
import AudioIcon from '../icons/AudioLines';

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

export type ExtraType = 'image' | 'audio' | 'video';
export interface ExtraItem {
type: ExtraType;
onChange?: () => void;
onFinish?: () => void;
render: () => JSX.Element;
}
export type ExtraModelProps = {
className?: string;
extra?: Array<ExtraItem | ExtraType>;
};

export const ExtraModel = (props: ExtraModelProps) => {
const { className, extra } = props;

const { localeObject } = useProChatLocale();

const defaultDoms = [
{
type: 'video',
render: <ActionIcon key={'video'} title={localeObject.video} icon={FileVideo} />,
},
{
type: 'audio',
render: <ActionIcon key={'audio'} title={localeObject.audio} icon={AudioIcon} />,
},
{
type: 'image',
render: <ActionIcon key={'image'} title={localeObject.image} icon={Image} />,
},
];

const { styles, theme } = useStyles();

const renderContent = () => {
if (!extra || extra.length === 0) {
return null;
}

const getDefaultRender = (type: ExtraType) => {
const defaultComponent = defaultDoms.find((dom) => dom.type === type);
return defaultComponent ? defaultComponent.render : null;
};

return extra.reverse().map((item) => {
if (isString(item)) {
return getDefaultRender(item as ExtraType);
} else if (isObject(item) && 'type' in item && 'render' in item) {
return item.render();
} else {
return null;
}
});
};
renderContent();
return (
<ConfigProvider theme={{ token: { colorText: theme.colorTextSecondary } }}>
<Flexbox
align={'center'}
direction={'horizontal-reverse'}
paddingInline={12}
className={cx(styles.extra, className)}
gap={8}
>
{renderContent()}
</Flexbox>
</ConfigProvider>
);
};

export default ExtraModel;
26 changes: 26 additions & 0 deletions src/ProInputArea/components/ProTextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Input } from 'antd';
import { TextAreaProps } from 'antd/es/input';
import { TextAreaRef } from 'antd/es/input/TextArea';
import React from 'react';

export const ProTextArea: React.FC<TextAreaProps> = React.forwardRef<TextAreaRef, TextAreaProps>(
(props, ref) => {
const { disabled, ...rest } = props;

return (
<Input.TextArea
size="large"
{...rest}
ref={ref}
disabled={disabled}
className={`${props.className}-textarea`}
onFocus={(e) => {
props.onFocus?.(e);
}}
onPressEnter={(e) => {
props.onPressEnter?.(e);
}}
/>
);
},
);
26 changes: 26 additions & 0 deletions src/ProInputArea/icons/AudioLines.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { memo } from 'react';

const AudioIcon = memo(() => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-audio-lines"
>
<path d="M2 10v3" />
<path d="M6 6v11" />
<path d="M10 3v18" />
<path d="M14 8v7" />
<path d="M18 5v13" />
<path d="M22 10v3" />
</svg>
);
});
export default AudioIcon;
Loading

0 comments on commit d2f5c07

Please sign in to comment.