Skip to content

Commit

Permalink
⚡️ fix: chatList更新后滑动到底部 (#310)
Browse files Browse the repository at this point in the history
* ⚡️ fix: chatList更新后滑动到底部

* ⚡️ fix: 简化chatlist逻辑
  • Loading branch information
222chaos authored Sep 3, 2024
1 parent 6c45cb9 commit e4b0004
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 115 deletions.
31 changes: 0 additions & 31 deletions src/components/ChatList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { useStyle } from './style';
export type ChatListProps = {
chatList: ChatMessage[];
chatListRef: MutableRefObject<HTMLDivElement>;
loadingMessage?: ChatMessage<any>;
loading: boolean;
className?: string;
chatItemRenderConfig: ChatItemProps['chatItemRenderConfig'];
Expand Down Expand Up @@ -50,7 +49,6 @@ const ChatList: React.FC<ChatListProps> = (props) => {
chatListItemAvatarClassName,
chatListItemContentClassName,
chatListItemTitleClassName,
loadingMessage,
userMeta = {
avatar: DEFAULT_USER_AVATAR,
},
Expand Down Expand Up @@ -104,35 +102,6 @@ const ChatList: React.FC<ChatListProps> = (props) => {
</ChatItem>
);
})}
{loadingMessage && (
<ChatItem
key={loadingMessage.id}
data-id={loadingMessage.id}
avatar={
(loadingMessage as any).meta ||
(loadingMessage.role === 'user' ? userMeta : assistantMeta)
}
animation
style={props.chatListItemStyle}
originData={loadingMessage}
placement={loadingMessage.role === 'user' ? 'right' : 'left'}
time={loadingMessage.updateAt || loadingMessage.createAt}
chatListItemContentStyle={{
...chatListItemContentStyle,
...(loadingMessage.role === 'user'
? chatListRightItemContentStyle
: chatListLeftItemContentStyle),
}}
chatListItemTitleStyle={chatListItemTitleStyle}
chatItemRenderConfig={chatItemRenderConfig}
chatListItemAvatarStyle={chatListItemAvatarStyle}
chatListItemAvatarClassName={chatListItemAvatarClassName}
chatListItemContentClassName={chatListItemContentClassName}
chatListItemTitleClassName={chatListItemTitleClassName}
>
<MessageComponent {...loadingMessage} />
</ChatItem>
)}
</div>,
);
};
Expand Down
10 changes: 3 additions & 7 deletions src/components/ProChat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,8 @@ export function ProChat<

const {
chatList,
loadingMessage,
loading,
isLoadingMessage,
setMessageItem,
stopGenerateMessage,
clearMessage,
Expand Down Expand Up @@ -410,8 +410,6 @@ export function ProChat<
: undefined,
});

const getChatLoadingMessage = useRefFunction(() => loadingMessage);

const getChatList = useRefFunction(() => {
return chatList;
});
Expand All @@ -422,7 +420,6 @@ export function ProChat<
clearMessage,
sendMessage,
getChatList,
getChatLoadingMessage,
setMessageItem,
genMessageRecord,
scrollToBottom: () => {
Expand Down Expand Up @@ -456,7 +453,7 @@ export function ProChat<
top: chatListContainerRef.current.scrollHeight,
});
}
}, [loadingMessage]);
}, [chatList]);

const backBottomDom = useMemo(() => {
if (!isInitRender) return null;
Expand Down Expand Up @@ -497,7 +494,6 @@ export function ProChat<
userMeta={userMeta}
assistantMeta={assistantMeta}
loading={loading}
loadingMessage={loadingMessage}
chatItemRenderConfig={chatItemRenderConfig}
style={{
...styles?.chatList,
Expand All @@ -520,7 +516,7 @@ export function ProChat<
{backBottomDom}
<ChatInputArea
className={classNames?.chatInputArea}
typing={!!loadingMessage?.id}
typing={isLoadingMessage}
placeholder={placeholder || '请输入消息...'}
onMessageSend={sendMessage}
mentionRequest={props.autocompleteRequest}
Expand Down
128 changes: 51 additions & 77 deletions src/hooks/useChatList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,6 @@ type ProChatUIUseListChatProps = {
export const useChatList = (props: ProChatUIUseListChatProps) => {
let controller = useRef<AbortController | null>(null);

const loadingMessageRef = useRef<ChatMessage<any> | undefined>(undefined);

const [loadingMessage, setLoadingMessage] = useMergedState<ChatMessage<any> | undefined>(
undefined,
{
postState: (value) => {
loadingMessageRef.current = value;
return value;
},
},
);

const getLoadingMessage = useRefFunction(() => {
return loadingMessageRef.current;
});

const chatListRef = useRef<ChatMessage<any>[]>([]);
/**
* Custom hook for managing the chat list.
Expand Down Expand Up @@ -144,7 +128,7 @@ export const useChatList = (props: ProChatUIUseListChatProps) => {
const [loading, setLoading] = useMergedState<boolean>(true, {
value: props.loading,
});

const [isLoadingMessage, setIsLoadingMessage] = useMergedState<boolean>(false);
/**
* Fetches the chat list using the provided request function.
* If the request function is not provided, it sets the loading state to false and returns.
Expand Down Expand Up @@ -198,7 +182,9 @@ export const useChatList = (props: ProChatUIUseListChatProps) => {
*/
const sendMessage = useRefFunction(async (message: string | Partial<ChatMessage>) => {
controller.current = new AbortController();
chatList.push(

setChatList((prevState) => [
...prevState,
genMessageRecord(
typeof message === 'string'
? { content: message }
Expand All @@ -208,22 +194,24 @@ export const useChatList = (props: ProChatUIUseListChatProps) => {
},
'user',
),
);
setChatList([...chatList]);
]);

if (!props?.sendMessageRequest) return;
setLoadingMessage(
setIsLoadingMessage(true);
setChatList((prevState) => [
...prevState,
genMessageRecord(
{
content: LOADING_FLAT,
},
'assistant',
),
);
]);

const res = (await Promise.race([
props.sendMessageRequest?.(chatListRef.current),
new Promise((_, reject) => {
controller.current.signal.addEventListener('abort', () => {
controller.current?.signal.addEventListener('abort', () => {
reject();
});
}),
Expand All @@ -233,75 +221,61 @@ export const useChatList = (props: ProChatUIUseListChatProps) => {
processSSE(res, {
signal: controller.current.signal,
onFinish: async () => {
setLoadingMessage(undefined);
setChatList((prevState) => {
const updatedList = [...prevState];
updatedList[updatedList.length - 1].isFinished = true;
return updatedList;
});
setIsLoadingMessage(false);
},
onMessageHandle: async (text, res, type) => {
if (type === 'done' || controller.current.signal.aborted) {
const message = getLoadingMessage();
if (!message) return;
setChatList((prev) => {
message.isFinished = true;
return [...prev, message];
if (type === 'done' || controller.current?.signal.aborted) {
setIsLoadingMessage(false);
setChatList((prevState) => {
const updatedList = [...prevState];
updatedList[updatedList.length - 1].isFinished = true;
return updatedList;
});
setLoadingMessage(undefined);
return;
}
const content =
getLoadingMessage()?.content === LOADING_FLAT
? text
: getLoadingMessage()?.content + text;

const message: ChatMessage = {
...getLoadingMessage(),
updateAt: Date.now(),
originContent: text,
isFinished: false,
content: content,
};
const transformMessage = await props.transformToChatMessage?.(message, {
preContent:
getLoadingMessage()?.content === LOADING_FLAT ? '' : getLoadingMessage()?.content,
currentContent: text,
setChatList((prevState) => {
const updatedList = [...prevState];
const currentMessage = updatedList[updatedList.length - 1];
currentMessage.content =
currentMessage.content === LOADING_FLAT ? text : currentMessage.content + text;
currentMessage.updateAt = Date.now();
return updatedList;
});

loadingMessageRef.current = transformMessage || message;
setLoadingMessage(transformMessage || message);
},
onErrorHandle: async (error) => {
const content = error.message;
const message = await props.transformToChatMessage?.(
{
...getLoadingMessage(),
updateAt: Date.now(),
content: content,
originContent: content,
},
{
preContent: getLoadingMessage()?.content,
currentContent: content,
},
);
setLoadingMessage(undefined);
setChatList((prev) => [...prev, message]);
setChatList((prevState) => {
const updatedList = [...prevState];
const currentMessage = updatedList[updatedList.length - 1];
currentMessage.content = content;
currentMessage.originContent = content;
currentMessage.updateAt = Date.now();
currentMessage.isFinished = true;
return updatedList;
});
setIsLoadingMessage(false);
},
});
} else {
const message = {
...getLoadingMessage(),
updateAt: Date.now(),
...res,
};

const transformChatMessage = await props.transformToChatMessage?.(message, {
preContent: getLoadingMessage()?.content,
currentContent: message.originContent,
setChatList((prevState) => {
const updatedList = [...prevState];
const currentMessage = {
...updatedList[updatedList.length - 1],
...res,
updateAt: Date.now(),
};
updatedList[updatedList.length - 1] = currentMessage;
return updatedList;
});

setLoadingMessage(undefined);
setChatList((prev) => [...prev, transformChatMessage || message]);
setIsLoadingMessage(false);
}
});

/**
* Stops the generation of messages.
*/
Expand Down Expand Up @@ -330,7 +304,7 @@ export const useChatList = (props: ProChatUIUseListChatProps) => {
return {
chatList: chatList.length > 0 ? chatList : helloMessageList,
loading,
loadingMessage,
isLoadingMessage,
stopGenerateMessage,
setMessageItem,
clearMessage,
Expand Down

0 comments on commit e4b0004

Please sign in to comment.