Skip to content

Commit 2b7fe8f

Browse files
authored
feat(agent-insights): Hide messages on long chat histories (#95098)
Hide message on long chat histories and add a button to show them. ![Screenshot 2025-07-09 at 09 23 02](https://github.com/user-attachments/assets/d148ff8e-b59c-4680-ba08-247d7f7accbf) - closes [TET-793: Show less/more button on a long history of messages](https://linear.app/getsentry/issue/TET-793/show-lessmore-button-on-a-long-history-of-messages)
1 parent 9f22be6 commit 2b7fe8f

File tree

1 file changed

+81
-23
lines changed
  • static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections

1 file changed

+81
-23
lines changed

static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput.tsx

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import {Fragment} from 'react';
1+
import {Fragment, useLayoutEffect, useState} from 'react';
2+
import styled from '@emotion/styled';
23
import * as Sentry from '@sentry/react';
34

5+
import {Button} from 'sentry/components/core/button';
46
import {t} from 'sentry/locale';
7+
import {space} from 'sentry/styles/space';
58
import type {EventTransaction} from 'sentry/types/event';
69
import {defined} from 'sentry/utils';
710
import useOrganization from 'sentry/utils/useOrganization';
11+
import usePrevious from 'sentry/utils/usePrevious';
812
import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails';
913
import {hasAgentInsightsFeature} from 'sentry/views/insights/agentMonitoring/utils/features';
1014
import {
@@ -17,6 +21,13 @@ import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/tr
1721
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
1822
import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode';
1923

24+
type AIMessageRole = 'system' | 'user' | 'assistant' | 'tool';
25+
26+
interface AIMessage {
27+
content: React.ReactNode;
28+
role: AIMessageRole;
29+
}
30+
2031
function renderTextMessages(content: any) {
2132
if (!Array.isArray(content)) {
2233
return content;
@@ -31,7 +42,7 @@ function renderToolMessage(content: any) {
3142
return content;
3243
}
3344

34-
function parseAIMessages(messages: string) {
45+
function parseAIMessages(messages: string): AIMessage[] | string {
3546
try {
3647
const array: any[] = Array.isArray(messages) ? messages : JSON.parse(messages);
3748
return array
@@ -134,7 +145,7 @@ function transformPrompt(prompt: string) {
134145
}
135146
}
136147

137-
const roleHeadings = {
148+
const roleHeadings: Record<AIMessageRole, string> = {
138149
system: t('System'),
139150
user: t('User'),
140151
assistant: t('Assistant'),
@@ -196,30 +207,77 @@ export function AIInputSection({
196207
<TraceDrawerComponents.MultilineText>
197208
{messages}
198209
</TraceDrawerComponents.MultilineText>
199-
) : messages ? (
200-
<Fragment>
201-
{messages.map((message, index) => (
202-
<Fragment key={index}>
203-
<TraceDrawerComponents.MultilineTextLabel>
204-
{roleHeadings[message.role]}
205-
</TraceDrawerComponents.MultilineTextLabel>
206-
{typeof message.content === 'string' ? (
207-
<TraceDrawerComponents.MultilineText>
208-
{message.content}
209-
</TraceDrawerComponents.MultilineText>
210-
) : (
211-
<TraceDrawerComponents.MultilineJSON
212-
value={message.content}
213-
maxDefaultDepth={2}
214-
/>
215-
)}
216-
</Fragment>
217-
))}
218-
</Fragment>
219210
) : null}
211+
{Array.isArray(messages) ? <MessagesArrayRenderer messages={messages} /> : null}
220212
{toolArgs ? (
221213
<TraceDrawerComponents.MultilineJSON value={toolArgs} maxDefaultDepth={1} />
222214
) : null}
223215
</FoldSection>
224216
);
225217
}
218+
219+
const MAX_MESSAGES_AT_START = 2;
220+
const MAX_MESSAGES_AT_END = 1;
221+
const MAX_MESSAGES_TO_SHOW = MAX_MESSAGES_AT_START + MAX_MESSAGES_AT_END;
222+
223+
/**
224+
* As the whole message history takes up too much space we only show the first two (as those often contain the system and initial user prompt)
225+
* and the last messages with the option to expand
226+
*/
227+
function MessagesArrayRenderer({messages}: {messages: AIMessage[]}) {
228+
const [isExpanded, setIsExpanded] = useState(messages.length <= MAX_MESSAGES_TO_SHOW);
229+
230+
// Reset the expanded state when the messages length changes
231+
const previousMessagesLength = usePrevious(messages.length);
232+
useLayoutEffect(() => {
233+
if (previousMessagesLength !== messages.length) {
234+
setIsExpanded(messages.length <= MAX_MESSAGES_TO_SHOW);
235+
}
236+
}, [messages.length, previousMessagesLength]);
237+
238+
const renderMessage = (message: AIMessage, index: number) => {
239+
return (
240+
<Fragment key={index}>
241+
<TraceDrawerComponents.MultilineTextLabel>
242+
{roleHeadings[message.role]}
243+
</TraceDrawerComponents.MultilineTextLabel>
244+
{typeof message.content === 'string' ? (
245+
<TraceDrawerComponents.MultilineText>
246+
{message.content}
247+
</TraceDrawerComponents.MultilineText>
248+
) : (
249+
<TraceDrawerComponents.MultilineJSON
250+
value={message.content}
251+
maxDefaultDepth={2}
252+
/>
253+
)}
254+
</Fragment>
255+
);
256+
};
257+
258+
if (isExpanded) {
259+
return messages.map(renderMessage);
260+
}
261+
262+
return (
263+
<Fragment>
264+
{messages.slice(0, MAX_MESSAGES_AT_START).map(renderMessage)}
265+
<ButtonDivider>
266+
<Button onClick={() => setIsExpanded(true)} size="xs">
267+
{t('+%s more messages', messages.length - MAX_MESSAGES_TO_SHOW)}
268+
</Button>
269+
</ButtonDivider>
270+
{messages.slice(-MAX_MESSAGES_AT_END).map(renderMessage)}
271+
</Fragment>
272+
);
273+
}
274+
275+
const ButtonDivider = styled('div')`
276+
height: 1px;
277+
width: 100%;
278+
border-bottom: 1px dashed ${p => p.theme.border};
279+
display: flex;
280+
justify-content: center;
281+
align-items: center;
282+
margin: ${space(4)} 0;
283+
`;

0 commit comments

Comments
 (0)