Skip to content

Commit

Permalink
✨ feat: support useChat and chatRef
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Nov 22, 2023
1 parent 8102ebe commit cf98b0f
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 66 deletions.
4 changes: 4 additions & 0 deletions .dumirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ export default defineConfig({
themeConfig: {
name: '@ant-design/pro-chat',
github: homepage,
siteToken: {
demoInheritSiteTheme: true,
},
},
mfsu: false,
outputPath: 'docs-dist',
html2sketch: {},
extraBabelPlugins: ['antd-style'],
Expand Down
48 changes: 45 additions & 3 deletions src/ProChat/container/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import StoreUpdater, { ProChatChatReference } from '@/ProChat/container/StoreUpdater';
import { memo, ReactNode } from 'react';
import { DevtoolsOptions } from 'zustand/middleware';
import { ChatProps, createStore, Provider, useStoreApi } from '../store';

interface ProChatProviderProps extends ChatProps {
children: ReactNode;
devtoolOptions?: boolean | DevtoolsOptions;
chatRef?: ProChatChatReference;
}

export const ProChatProvider = memo<ProChatProviderProps>(
({ children, devtoolOptions, ...props }) => {
({
children,
devtoolOptions,
chats,
onChatsChange,
loading,
helloMessage,
userMeta,
assistantMeta,
request,
chatRef,
...props
}) => {
let isWrapped = true;

const Content = <>{children}</>;
const Content = (
<>
{children}
<StoreUpdater
chatRef={chatRef}
init={!loading}
helloMessage={helloMessage}
chats={chats}
userMeta={userMeta}
request={request}
assistantMeta={assistantMeta}
onChatsChange={onChatsChange}
/>
</>
);

try {
useStoreApi();
Expand All @@ -23,6 +51,20 @@ export const ProChatProvider = memo<ProChatProviderProps>(
return Content;
}

return <Provider createStore={() => createStore(props, devtoolOptions)}>{Content}</Provider>;
return (
<Provider createStore={() => createStore(props, devtoolOptions)}>
{Content}
<StoreUpdater
chatRef={chatRef}
init={!loading}
helloMessage={helloMessage}
chats={chats}
userMeta={userMeta}
request={request}
assistantMeta={assistantMeta}
onChatsChange={onChatsChange}
/>
</Provider>
);
},
);
32 changes: 26 additions & 6 deletions src/ProChat/container/StoreUpdater.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { memo } from 'react';
import { memo, MutableRefObject, useImperativeHandle } from 'react';
import { createStoreUpdater } from 'zustand-utils';

import { ProChatInstance, useProChat } from '../hooks/useProChat';
import { ChatProps, ChatState, useStoreApi } from '../store';

export type StoreUpdaterProps = Partial<
Pick<ChatState, 'chats' | 'config' | 'init' | 'onChatsChange' | 'helloMessage' | 'request'>
> &
Pick<ChatProps, 'userMeta' | 'assistantMeta'>;
export type ProChatChatReference = MutableRefObject<ProChatInstance | undefined>;

export interface StoreUpdaterProps
extends Partial<
Pick<ChatState, 'chats' | 'config' | 'init' | 'onChatsChange' | 'helloMessage' | 'request'>
>,
Pick<ChatProps, 'userMeta' | 'assistantMeta'> {
chatRef?: ProChatChatReference;
}

const StoreUpdater = memo<StoreUpdaterProps>(
({ init, onChatsChange, request, userMeta, assistantMeta, helloMessage, chats, config }) => {
({
init,
onChatsChange,
chatRef,
request,
userMeta,
assistantMeta,
helloMessage,
chats,
config,
}) => {
const storeApi = useStoreApi();
const useStoreUpdater = createStoreUpdater(storeApi);

Expand All @@ -25,6 +41,10 @@ const StoreUpdater = memo<StoreUpdaterProps>(
useStoreUpdater('onChatsChange', onChatsChange);

useStoreUpdater('request', request);

const instance = useProChat();
useImperativeHandle(chatRef, () => instance);

return null;
},
);
Expand Down
45 changes: 4 additions & 41 deletions src/ProChat/container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,24 @@ import App from './App';
import { DevtoolsOptions } from 'zustand/middleware';
import { ChatProps } from '../store';
import { ProChatProvider } from './Provider';
import StoreUpdater from './StoreUpdater';
import { ProChatChatReference } from './StoreUpdater';

export interface ProChatProps extends ChatProps {
renderInput?: ReactNode;
__PRO_CHAT_STORE_DEVTOOLS__?: boolean | DevtoolsOptions;
showTitle?: boolean;
style?: CSSProperties;
className?: string;
chatRef?: ProChatChatReference;
}

export const ProChat = memo<ProChatProps>(
({
renderInput,
__PRO_CHAT_STORE_DEVTOOLS__,
chats,
onChatsChange,
initialChats,
loading,
helloMessage,
userMeta,
assistantMeta,
showTitle,
request,
onResetMessage,
style,
className,
autocompleteRequest,
...props
}) => {
({ renderInput, __PRO_CHAT_STORE_DEVTOOLS__, showTitle, style, className, ...props }) => {
return (
<ProChatProvider
initialChats={initialChats}
chats={chats}
loading={loading}
helloMessage={helloMessage}
userMeta={userMeta}
assistantMeta={assistantMeta}
request={request}
onResetMessage={onResetMessage}
autocompleteRequest={autocompleteRequest}
{...props}
devtoolOptions={__PRO_CHAT_STORE_DEVTOOLS__}
>
<ProChatProvider {...props} devtoolOptions={__PRO_CHAT_STORE_DEVTOOLS__}>
<Container>
<App chatInput={renderInput} showTitle={showTitle} style={style} className={className} />
</Container>
<StoreUpdater
init={!loading}
helloMessage={helloMessage}
chats={chats}
userMeta={userMeta}
request={request}
assistantMeta={assistantMeta}
onChatsChange={onChatsChange}
/>
</ProChatProvider>
);
},
Expand Down
2 changes: 1 addition & 1 deletion src/ProChat/demos/initialChats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default () => {
const theme = useTheme();
return (
<div style={{ background: theme.colorBgLayout }}>
<ProChat initialChats={example.chats} config={example.config} />
<ProChat initialChats={example.chats} />
</div>
);
};
2 changes: 1 addition & 1 deletion src/ProChat/demos/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default () => {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
title: 'Ant Design',
}}
assistantMeta={{ avatar: '🛸', title: '三体世界', backgroundColor: 'blue' }}
assistantMeta={{ avatar: '🛸', title: '三体世界', backgroundColor: '#67dedd' }}
initialChats={chats.chats}
/>
</div>
Expand Down
100 changes: 100 additions & 0 deletions src/ProChat/demos/use-pro-chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* compact: true
*/
import { ProChat, ProChatProvider, useProChat } from '@ant-design/pro-chat';
import { Button, Divider, Flex, message } from 'antd';
import { useTheme } from 'antd-style';

import { MockResponse } from '@/ProChat/mocks/streamResponse';
import { example } from '../mocks/basic';

const Chat = () => {
const theme = useTheme();
return (
<div style={{ background: theme.colorBgLayout }}>
<ProChat
request={async (messages) => {
const mockedData: string = `这是一段模拟的流式字符串数据。本次会话传入了${messages.length}条消息`;

const mockResponse = new MockResponse(mockedData, 100);

return mockResponse.getResponse();
}}
/>
</div>
);
};

const Control = () => {
const proChat = useProChat();

return (
<Flex style={{ padding: 24 }} gap={8} justify={'space-between'}>
<Flex gap={8}>
<Button
type={'primary'}
onClick={() => {
proChat.sendMessage('这是程序化发送的消息');
}}
>
发送一条消息
</Button>
<Button
onClick={() => {
const messages = proChat.getChatMessages();

const msg = messages.at(-1);
if (msg) {
message.info(msg.content);
} else {
message.warning('会话为空');
}
}}
>
获取最新会话消息
</Button>
</Flex>

<Button
onClick={() => {
const messages = proChat.getChatMessages();
const { id, content } = messages[0] || {};

if (!id) return;
proChat.setMessageContent(id, content + '👋');
}}
>
修改首条消息,添加表情:👋
</Button>
<Flex gap={8}>
<Button
danger
onClick={() => {
const messages = proChat.getChatMessages();
proChat.deleteMessage(messages[0].id);
message.success('已删除第一条消息');
}}
>
删除第一条消息
</Button>
<Button
type={'primary'}
danger
onClick={() => {
proChat.clearMessage();
}}
>
清空消息
</Button>
</Flex>
</Flex>
);
};

export default () => (
<ProChatProvider initialChats={example.chats}>
<Control />
<Divider>🔼 程序化控制 | 🔽 用户控制</Divider>
<Chat />
</ProChatProvider>
);
44 changes: 44 additions & 0 deletions src/ProChat/demos/use-ref.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* compact: true
*/
import { ProChat, ProChatInstance } from '@ant-design/pro-chat';
import { useTheme } from 'antd-style';
import { useRef } from 'react';

import { MockResponse } from '@/ProChat/mocks/streamResponse';
import { Button } from 'antd';
import { example } from '../mocks/basic';

export default () => {
const theme = useTheme();
const proChatRef = useRef<ProChatInstance>();

return (
<div style={{ background: theme.colorBgLayout }}>
<Button
type={'primary'}
onClick={() => {
if (!proChatRef.current) return;
const messages = proChatRef.current.getChatMessages();
const { id, content } = messages[0] || {};

if (!id) return;
proChatRef.current.setMessageContent(id, content + '👋');
}}
>
修改首条消息,添加表情:👋
</Button>
<ProChat
initialChats={example.chats}
chatRef={proChatRef}
request={async (messages) => {
const mockedData: string = `这是一段模拟的流式字符串数据。本次会话传入了${messages.length}条消息`;

const mockResponse = new MockResponse(mockedData, 100);

return mockResponse.getResponse();
}}
/>
</div>
);
};
Loading

0 comments on commit cf98b0f

Please sign in to comment.