Skip to content

Commit c0db930

Browse files
michaelchiadlqqq
andauthored
Group messages with their replies (#832)
* group messages with their replies * remove console.log * lint * sort by timestamp Co-authored-by: david qiu <[email protected]> * sortedMessages as state variable --------- Co-authored-by: david qiu <[email protected]>
1 parent 02d1966 commit c0db930

File tree

1 file changed

+52
-3
lines changed

1 file changed

+52
-3
lines changed

packages/jupyter-ai/src/components/chat-messages.tsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,49 @@ type ChatMessageHeaderProps = {
2121
sx?: SxProps<Theme>;
2222
};
2323

24+
function sortMessages(
25+
messages: AiService.ChatMessage[]
26+
): AiService.ChatMessage[] {
27+
const timestampsById: Record<string, number> = {};
28+
for (const message of messages) {
29+
timestampsById[message.id] = message.time;
30+
}
31+
32+
return [...messages].sort((a, b) => {
33+
/**
34+
* Use the *origin timestamp* as the primary sort key. This ensures that
35+
* each agent reply is grouped with the human message that triggered it.
36+
*
37+
* - If the message is from an agent, the origin timestamp is the timestamp
38+
* of the message it is replying to.
39+
*
40+
* - Otherwise, the origin timestamp is the *message timestamp*, i.e.
41+
* `message.time` itself.
42+
*/
43+
44+
const aOriginTimestamp =
45+
a.type === 'agent' && a.reply_to in timestampsById
46+
? timestampsById[a.reply_to]
47+
: a.time;
48+
const bOriginTimestamp =
49+
b.type === 'agent' && b.reply_to in timestampsById
50+
? timestampsById[b.reply_to]
51+
: b.time;
52+
53+
/**
54+
* Use the message timestamp as a secondary sort key. This ensures that each
55+
* agent reply is shown after the human message that triggered it.
56+
*/
57+
const aMessageTimestamp = a.time;
58+
const bMessageTimestamp = b.time;
59+
60+
return (
61+
aOriginTimestamp - bOriginTimestamp ||
62+
aMessageTimestamp - bMessageTimestamp
63+
);
64+
});
65+
}
66+
2467
export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
2568
const collaborators = useCollaboratorsContext();
2669

@@ -104,6 +147,9 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
104147

105148
export function ChatMessages(props: ChatMessagesProps): JSX.Element {
106149
const [timestamps, setTimestamps] = useState<Record<string, string>>({});
150+
const [sortedMessages, setSortedMessages] = useState<AiService.ChatMessage[]>(
151+
[]
152+
);
107153

108154
/**
109155
* Effect: update cached timestamp strings upon receiving a new message.
@@ -129,6 +175,10 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element {
129175
}
130176
}, [props.messages]);
131177

178+
useEffect(() => {
179+
setSortedMessages(sortMessages(props.messages));
180+
}, [props.messages]);
181+
132182
return (
133183
<Box
134184
sx={{
@@ -137,15 +187,14 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element {
137187
}
138188
}}
139189
>
140-
{props.messages.map((message, i) => {
190+
{sortedMessages.map(message => {
141191
// render selection in HumanChatMessage, if any
142192
const markdownStr =
143193
message.type === 'human' && message.selection
144194
? message.body + '\n\n```\n' + message.selection.source + '\n```\n'
145195
: message.body;
146-
147196
return (
148-
<Box key={i} sx={{ padding: 4 }}>
197+
<Box key={message.id} sx={{ padding: 4 }}>
149198
<ChatMessageHeader
150199
message={message}
151200
timestamp={timestamps[message.id]}

0 commit comments

Comments
 (0)