Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡️ fix: chatList更新后滑动到底部 #310

Merged
merged 2 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading