From e31a4036329a7d59737ed9329a8479dd3b157510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9F=E8=B4=A4?= Date: Fri, 2 Feb 2024 19:45:46 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20chats=20should=20use=20arra?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guide/demos/doc-mode.tsx | 2 +- docs/guide/initial.md | 2 +- src/ProChat/container/App.tsx | 4 +-- src/ProChat/container/index.tsx | 6 ++--- src/ProChat/demos/backtoBottomConfig.tsx | 4 +-- src/ProChat/demos/bigData.tsx | 31 +++++++--------------- src/ProChat/demos/control.tsx | 5 ++-- src/ProChat/demos/doc-mode.tsx | 2 +- src/ProChat/demos/initialChats.tsx | 2 +- src/ProChat/demos/use-pro-chat.tsx | 2 +- src/ProChat/demos/use-ref.tsx | 2 +- src/ProChat/index.md | 12 ++++----- src/ProChat/store/initialState.ts | 8 +++--- src/ProChat/store/reducers/message.test.ts | 25 ++++++++--------- src/ProChat/store/reducers/message.ts | 20 +++++++------- src/ProChat/store/selectors/chat.ts | 4 +-- src/ProChat/store/store.ts | 4 ++- src/types/message.ts | 4 --- 18 files changed, 63 insertions(+), 76 deletions(-) diff --git a/docs/guide/demos/doc-mode.tsx b/docs/guide/demos/doc-mode.tsx index c5555a28..2780f162 100644 --- a/docs/guide/demos/doc-mode.tsx +++ b/docs/guide/demos/doc-mode.tsx @@ -21,7 +21,7 @@ export default () => { return mockResponse.getResponse(); }} - chats={example.chats} + chats={Object.values(example.chats)} config={example.config} /> diff --git a/docs/guide/initial.md b/docs/guide/initial.md index a8e2c146..0f62cebb 100644 --- a/docs/guide/initial.md +++ b/docs/guide/initial.md @@ -56,7 +56,7 @@ export default () => { }; ``` -上述代码中,我们将 `example.chats` 作为初始聊天数据传递给 ProChat 组件。注意,在使用该属性时需要提供正确格式的初始聊天数据。 +上述代码中,我们将 `Object.values(example.chats)` 作为初始聊天数据传递给 ProChat 组件。注意,在使用该属性时需要提供正确格式的初始聊天数据。 ## 使用骨架屏 diff --git a/src/ProChat/container/App.tsx b/src/ProChat/container/App.tsx index 5b3bd95b..badd70e5 100644 --- a/src/ProChat/container/App.tsx +++ b/src/ProChat/container/App.tsx @@ -44,7 +44,7 @@ const App = memo( chatRef, itemShouldUpdate, chatItemRenderConfig, - backtoBottomConfig, + backToBottomConfig, }) => { const ref = useRef(null); const areaHtml = useRef(null); @@ -106,7 +106,7 @@ const App = memo( }} target={ref} text={'返回底部'} - {...backtoBottomConfig} + {...backToBottomConfig} /> ) : null} diff --git a/src/ProChat/container/index.tsx b/src/ProChat/container/index.tsx index 1af2d9e4..8ef9f2fd 100644 --- a/src/ProChat/container/index.tsx +++ b/src/ProChat/container/index.tsx @@ -21,7 +21,7 @@ export interface ProChatProps> extends ChatProps; + backToBottomConfig?: Omit; } export function ProChat = Record>({ @@ -31,7 +31,7 @@ export function ProChat = Record>({ style, className, chatItemRenderConfig, - backtoBottomConfig, + backToBottomConfig, appStyle, ...props }: ProChatProps) { @@ -52,7 +52,7 @@ export function ProChat = Record>({ chatRef={props.chatRef} showTitle={showTitle} style={style} - backtoBottomConfig={backtoBottomConfig} + backToBottomConfig={backToBottomConfig} className={className} /> diff --git a/src/ProChat/demos/backtoBottomConfig.tsx b/src/ProChat/demos/backtoBottomConfig.tsx index 0d8dc54f..30b1fd21 100644 --- a/src/ProChat/demos/backtoBottomConfig.tsx +++ b/src/ProChat/demos/backtoBottomConfig.tsx @@ -15,9 +15,9 @@ export default () => { { return ( { const mockedData: string = `这是一段模拟的流式字符串数据。本次会话传入了${messages.length}条消息`; diff --git a/src/ProChat/index.md b/src/ProChat/index.md index 05e724b4..a5297868 100644 --- a/src/ProChat/index.md +++ b/src/ProChat/index.md @@ -72,9 +72,9 @@ ProChat 使用 `meta` 来表意会话双方的头像、名称等信息。设定 ## 自定义「回到底部」按钮 -你可以通过 backtoBottomConfig 参数对「回到底部」按钮进行不同程度的自定义 +你可以通过 backToBottomConfig 参数对「回到底部」按钮进行不同程度的自定义 - + ## 自定义输入区域 @@ -129,8 +129,8 @@ useProChat hooks 必须在包裹 `ProChatProvider` 后方可使用。 | userMeta | 用户元数据 | MetaData | - | | assistantMeta | 助手元数据 | MetaData | - | | config | 语言模型角色设定 | ModelConfig | - | -| chats | 聊天记录 | ChatMessageMap | - | -| onChatsChange | 聊天记录变化回调函数, | (chats: ChatMessageMap) => void | chat | +| chats | 聊天记录 | ChatMessage[] | - | +| onChatsChange | 聊天记录变化回调函数, | (chats: ChatMessage[]) => void | chat | | displayMode | 显示模式,默认是 chat | 'chat' \| 'docs' | - | | helloMessage | 欢迎消息 | string\| ReactNode | - | | request | 请求消息 | string \| ChatRequest | - | @@ -144,7 +144,7 @@ useProChat hooks 必须在包裹 `ProChatProvider` 后方可使用。 | actions.flexConfig | 控制 input 顶部的操作区域的 flex 布局 | `FlexBasicProps` | - | | actions.render | 控制 input 顶部的操作区域的操作按钮 | `(defaultDoms: JSX.Element[]) => JSX.Element[]` | - | | chatItemRenderConfig | 聊天项渲染配置 | `ChatItemRenderConfig` | - | -| backtoBottomConfig | 透传给「回到底部」组件的 api | `BackBottomProps` | - | +| backToBottomConfig | 透传给「回到底部」组件的 api | `BackBottomProps` | - | ## ProChatChatReference @@ -171,7 +171,7 @@ useProChat hooks 必须在包裹 `ProChatProvider` 后方可使用。 | avatarRender | 头像渲染函数 | WithFalse<(props: ChatItemProps, defaultDom: ReactNode) => ReactNode> | - | | render | 自定义渲染函数 | WithFalse<(props: ChatItemProps, defaultDom: { avatar: ReactNode; title: ReactNode; messageContent: ReactNode; actions: ReactNode; itemDom: ReactNode; }) => ReactNode> | - | -## backtoBottomConfig +## backToBottomConfig | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | diff --git a/src/ProChat/store/initialState.ts b/src/ProChat/store/initialState.ts index 7cb5a5c5..223d93ec 100644 --- a/src/ProChat/store/initialState.ts +++ b/src/ProChat/store/initialState.ts @@ -1,7 +1,7 @@ import { DEFAULT_AVATAR, DEFAULT_USER_AVATAR } from '@/ProChat/const/meta'; import { ModelConfig } from '@/ProChat/types/config'; import { MetaData } from '@/ProChat/types/meta'; -import { ChatMessage, ChatMessageMap } from '@/types/message'; +import { ChatMessage } from '@/types/message'; import { TextAreaProps } from 'antd/es/input'; import { ReactNode } from 'react'; import { FlexBasicProps } from 'react-layout-kit/lib/FlexBasic'; @@ -17,9 +17,9 @@ export interface ChatPropsState = Record; + chats: ChatMessage[]; + onChatsChange?: (chats: ChatMessage[]) => void; chatRef?: ProChatChatReference; - onChatsChange?: (chats: ChatMessageMap) => void; displayMode: 'chat' | 'docs'; userMeta: MetaData; assistantMeta: MetaData; @@ -105,7 +105,7 @@ export const initialModelConfig: ModelConfig = { }; export const initialState: ChatState = { - chats: {}, + chats: [], init: true, displayMode: 'chat', userMeta: { diff --git a/src/ProChat/store/reducers/message.test.ts b/src/ProChat/store/reducers/message.test.ts index 6dda72b8..fc2e2eb5 100644 --- a/src/ProChat/store/reducers/message.test.ts +++ b/src/ProChat/store/reducers/message.test.ts @@ -1,9 +1,9 @@ -import { ChatMessageMap } from '@/types/message'; +import { ChatMessage } from '@/types/message'; import { MessageDispatch, messagesReducer } from './message'; describe('messagesReducer', () => { - let initialState: ChatMessageMap; + let initialState: ChatMessage[]; beforeEach(() => { initialState = { @@ -21,7 +21,7 @@ describe('messagesReducer', () => { updateAt: 1629264000000, role: 'system', }, - } as unknown as ChatMessageMap; + } as unknown as ChatMessage[]; }); describe('addMessage', () => { @@ -39,7 +39,7 @@ describe('messagesReducer', () => { expect(newState).toHaveProperty('message1'); expect(newState).toHaveProperty('message2'); expect(newState).toHaveProperty('message3'); - expect(newState.message3).toEqual({ + expect(newState.find((m) => m.id === 'message3')).toEqual({ id: 'message3', content: 'New Message', createAt: expect.any(Number), @@ -62,7 +62,7 @@ describe('messagesReducer', () => { expect(newState).toHaveProperty('message1'); expect(newState).toHaveProperty('message2'); expect(newState).toHaveProperty('customId'); - expect(newState.customId).toEqual({ + expect(newState.find((m) => m.id === 'customId')).toEqual({ id: 'customId', content: 'New Message', createAt: expect.any(Number), @@ -82,7 +82,7 @@ describe('messagesReducer', () => { const newState = messagesReducer(initialState, payload); - expect(newState.message3).toEqual({ + expect(newState.find((m) => m.id === 'message3')).toEqual({ id: 'message3', content: 'New Message', createAt: expect.any(Number), @@ -103,7 +103,7 @@ describe('messagesReducer', () => { const newState = messagesReducer(initialState, payload); - expect(newState.message3).toEqual({ + expect(newState.find((m) => m.id === 'message3')).toEqual({ id: 'message3', content: 'New Message', createAt: expect.any(Number), @@ -151,8 +151,9 @@ describe('messagesReducer', () => { const newState = messagesReducer(initialState, payload); - expect(newState.message1.content).toBe('Updated Message'); - expect(newState.message1.updateAt).toBeGreaterThan(initialState.message1.updateAt); + const message1 = newState.find((m) => m.id === 'message1'); + expect(message1?.content).toBe('Updated Message'); + expect(message1?.updateAt).toBeGreaterThan(message1?.updateAt as number); }); it('should not modify the state if the specified message does not exist', () => { @@ -179,9 +180,9 @@ describe('messagesReducer', () => { }; const newState = messagesReducer(initialState, payload); - - expect(newState.message1.extra!.translate).toEqual({ target: 'en', to: 'zh' }); - expect(newState.message1.updateAt).toBeGreaterThan(initialState.message1.updateAt); + const message1 = newState.find((m) => m.id === 'message1'); + expect(message1?.extra!.translate).toEqual({ target: 'en', to: 'zh' }); + expect(message1?.updateAt).toBeGreaterThan(message1?.updateAt as number); }); it('should not modify the state if the specified message does not exist', () => { diff --git a/src/ProChat/store/reducers/message.ts b/src/ProChat/store/reducers/message.ts index a046fb9e..ad04b307 100644 --- a/src/ProChat/store/reducers/message.ts +++ b/src/ProChat/store/reducers/message.ts @@ -1,7 +1,7 @@ import { produce } from 'immer'; import { LLMRoleType } from '@/types/llm'; -import { ChatMessage, ChatMessageMap } from '@/types/message'; +import { ChatMessage } from '@/types/message'; import { MetaData } from '@/types/meta'; import { nanoid } from '../../utils/uuid'; @@ -46,35 +46,36 @@ export type MessageDispatch = | UpdateMessageExtra; export const messagesReducer = ( - state: ChatMessageMap, + state: ChatMessage[], payload: MessageDispatch, -): ChatMessageMap => { +): ChatMessage[] => { switch (payload.type) { case 'addMessage': { return produce(state, (draftState) => { const mid = payload.id || nanoid(); - draftState[mid] = { + draftState.push({ content: payload.message, createAt: Date.now(), id: mid, parentId: payload.parentId, role: payload.role, updateAt: Date.now(), - }; + }); }); } case 'deleteMessage': { return produce(state, (draftState) => { - delete draftState[payload.id]; + const index = draftState.findIndex((m) => m.id === payload.id); + delete draftState[index]; }); } case 'updateMessage': { return produce(state, (draftState) => { const { id, key, value } = payload; - const message = draftState[id]; + const message = draftState.find((m) => m.id === id); if (!message) return; // @ts-ignore @@ -86,7 +87,7 @@ export const messagesReducer = ( case 'updateMessageExtra': { return produce(state, (draftState) => { const { id, key, value } = payload; - const message = draftState[id]; + const message = draftState.find((m) => m.id === id); if (!message) return; if (!message.extra) { @@ -105,7 +106,8 @@ export const messagesReducer = ( // 删除上述找到的消息 for (const message of messages) { - delete draftState[message.id]; + const index = draftState.findIndex((m) => m.id === message.id); + delete draftState[index]; } }); } diff --git a/src/ProChat/store/selectors/chat.ts b/src/ProChat/store/selectors/chat.ts index 29cf180e..f00a6451 100644 --- a/src/ProChat/store/selectors/chat.ts +++ b/src/ProChat/store/selectors/chat.ts @@ -37,9 +37,7 @@ export const currentChats = (s: ChatStore): ChatMessage[] => { return {}; }; - const basic = Object.values(s.chats) - // 首先按照时间顺序排序,越早的在越前面 - .sort((pre, next) => pre.createAt - next.createAt) + const basic = s.chats // 映射头像关系 .map((m) => { return { diff --git a/src/ProChat/store/store.ts b/src/ProChat/store/store.ts index d669462d..cfaa7982 100644 --- a/src/ProChat/store/store.ts +++ b/src/ProChat/store/store.ts @@ -41,9 +41,11 @@ const vanillaStore = (...parameters) => { // initState = innerState + props + const finalInitChats = chats ?? initialChats; + const state = merge({}, initialState, { init: !loading, - chats: chats ?? initialChats, + chats: Array.isArray(finalInitChats) ? finalInitChats : Object.values(finalInitChats || {}), ...props, } as ChatState); diff --git a/src/types/message.ts b/src/types/message.ts index 195c0ccf..a83c85ba 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -31,10 +31,6 @@ export interface ChatMessage = Record extra?: T; } -export type ChatMessageMap = Record> = Record< - string, - ChatMessage ->; export interface OpenAIFunctionCall { arguments?: string; name: string;