From 06f4170767b1cbecada724064481a8079aea3e59 Mon Sep 17 00:00:00 2001 From: michaelchia Date: Thu, 20 Jun 2024 01:29:50 +0800 Subject: [PATCH] Group messages with their replies (#832) * group messages with their replies * remove console.log * lint * sort by timestamp Co-authored-by: david qiu * sortedMessages as state variable --------- Co-authored-by: david qiu --- .../src/components/chat-messages.tsx | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/packages/jupyter-ai/src/components/chat-messages.tsx b/packages/jupyter-ai/src/components/chat-messages.tsx index 0559387f2..77fbbbfd1 100644 --- a/packages/jupyter-ai/src/components/chat-messages.tsx +++ b/packages/jupyter-ai/src/components/chat-messages.tsx @@ -21,6 +21,49 @@ type ChatMessageHeaderProps = { sx?: SxProps; }; +function sortMessages( + messages: AiService.ChatMessage[] +): AiService.ChatMessage[] { + const timestampsById: Record = {}; + for (const message of messages) { + timestampsById[message.id] = message.time; + } + + return [...messages].sort((a, b) => { + /** + * Use the *origin timestamp* as the primary sort key. This ensures that + * each agent reply is grouped with the human message that triggered it. + * + * - If the message is from an agent, the origin timestamp is the timestamp + * of the message it is replying to. + * + * - Otherwise, the origin timestamp is the *message timestamp*, i.e. + * `message.time` itself. + */ + + const aOriginTimestamp = + a.type === 'agent' && a.reply_to in timestampsById + ? timestampsById[a.reply_to] + : a.time; + const bOriginTimestamp = + b.type === 'agent' && b.reply_to in timestampsById + ? timestampsById[b.reply_to] + : b.time; + + /** + * Use the message timestamp as a secondary sort key. This ensures that each + * agent reply is shown after the human message that triggered it. + */ + const aMessageTimestamp = a.time; + const bMessageTimestamp = b.time; + + return ( + aOriginTimestamp - bOriginTimestamp || + aMessageTimestamp - bMessageTimestamp + ); + }); +} + export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element { const collaborators = useCollaboratorsContext(); @@ -104,6 +147,9 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element { export function ChatMessages(props: ChatMessagesProps): JSX.Element { const [timestamps, setTimestamps] = useState>({}); + const [sortedMessages, setSortedMessages] = useState( + [] + ); /** * Effect: update cached timestamp strings upon receiving a new message. @@ -129,6 +175,10 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element { } }, [props.messages]); + useEffect(() => { + setSortedMessages(sortMessages(props.messages)); + }, [props.messages]); + return ( - {props.messages.map((message, i) => { + {sortedMessages.map(message => { // render selection in HumanChatMessage, if any const markdownStr = message.type === 'human' && message.selection ? message.body + '\n\n```\n' + message.selection.source + '\n```\n' : message.body; - return ( - +