diff --git a/static/app/components/core/badge/tag.tsx b/static/app/components/core/badge/tag.tsx index a5f9dc6600c55e..3bf745b3450f1c 100644 --- a/static/app/components/core/badge/tag.tsx +++ b/static/app/components/core/badge/tag.tsx @@ -115,6 +115,7 @@ const Text = styled('div')` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + min-width: 0; /* @TODO(jonasbadalic): Some occurrences pass other things than strings into the children prop. */ display: flex; diff --git a/static/app/views/explore/conversations/components/conversationSummary.tsx b/static/app/views/explore/conversations/components/conversationSummary.tsx index 8fa02c88042728..573fff36e9ba16 100644 --- a/static/app/views/explore/conversations/components/conversationSummary.tsx +++ b/static/app/views/explore/conversations/components/conversationSummary.tsx @@ -5,6 +5,7 @@ import styled from '@emotion/styled'; import {Tag} from '@sentry/scraps/badge'; import {Button} from '@sentry/scraps/button'; +import {InfoText} from '@sentry/scraps/info'; import {Flex} from '@sentry/scraps/layout'; import {Link} from '@sentry/scraps/link'; import {Heading, Text} from '@sentry/scraps/text'; @@ -45,7 +46,7 @@ interface ConversationSummaryProps { } const VISIBLE_TRACE_COUNT = 5; -const VISIBLE_TOOL_COUNT = 5; +const VISIBLE_TOOL_COUNT = 4; function getTraceUrl(orgSlug: string, traceId: string, spanId: string) { return normalizeUrl( @@ -128,7 +129,7 @@ export function ConversationAggregatesBar({ const errorsUrl = getExploreUrl({ organization, selection, - query: `gen_ai.conversation.id:"${conversationId.replace(/"/g, '\\"')}" span.status:internal_error`, + query: `gen_ai.conversation.id:"${conversationId.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" span.status:internal_error`, }); return ( @@ -189,24 +190,22 @@ export function ConversationAggregatesBar({ ))} {aggregates.toolNames.length > VISIBLE_TOOL_COUNT && ( - - {t('+%s more', aggregates.toolNames.length - VISIBLE_TOOL_COUNT)} - + variant="muted" + wrap="nowrap" + title={ + + {aggregates.toolNames.slice(VISIBLE_TOOL_COUNT).map(name => ( + + {name} + + ))} + } - triggerProps={{ - size: 'zero', - variant: 'transparent', - showChevron: false, - }} - items={aggregates.toolNames.slice(VISIBLE_TOOL_COUNT).map(name => ({ - key: name, - label: {name}, - textValue: name, - }))} - /> + > + {t('+%s more', aggregates.toolNames.length - VISIBLE_TOOL_COUNT)} + )} ) @@ -228,7 +227,9 @@ export function ConversationSummary({ }, [nodes]); const handleCopyConversationId = () => { - trackAnalytics('conversations.detail.copy-conversation-id', {organization}); + trackAnalytics('conversations.detail.copy-conversation-id', { + organization, + }); copyToClipboard(conversationId, { successMessage: t('Copied conversation ID to clipboard'), }); @@ -331,7 +332,9 @@ export function ConversationSummary({ isLoading={isLoading} lastMessageDate={lastMessageDate} onErrorsLinkClick={() => - trackAnalytics('conversations.detail.click-errors-link', {organization}) + trackAnalytics('conversations.detail.click-errors-link', { + organization, + }) } /> diff --git a/static/app/views/explore/conversations/components/messageToolCalls.tsx b/static/app/views/explore/conversations/components/messageToolCalls.tsx index 5b76868407712d..0e57989a97bed4 100644 --- a/static/app/views/explore/conversations/components/messageToolCalls.tsx +++ b/static/app/views/explore/conversations/components/messageToolCalls.tsx @@ -1,4 +1,5 @@ -import styled from '@emotion/styled'; +import {css} from '@emotion/react'; +import {useTheme} from '@emotion/react'; import {Tag} from '@sentry/scraps/badge'; import {Container, Flex} from '@sentry/scraps/layout'; @@ -19,6 +20,12 @@ interface MessageToolCallsProps { toolCalls: ToolCall[]; } +const hoverStyle = css` + &:hover { + opacity: 0.85; + } +`; + export function MessageToolCalls({ toolCalls, selectedNodeId, @@ -26,18 +33,29 @@ export function MessageToolCalls({ onSelectNode, }: MessageToolCallsProps) { const organization = useOrganization(); + const theme = useTheme(); + return ( {toolCalls.map(tool => { const toolNode = nodeMap.get(tool.nodeId); const isToolSelected = tool.nodeId === selectedNodeId; return ( - { e.stopPropagation(); trackAnalytics('conversations.message.click-tool-call', {organization}); @@ -52,17 +70,15 @@ export function MessageToolCalls({ {t('Called tool')} - : undefined} - hasError={tool.hasError} - isSelected={isToolSelected} > {tool.name} - + {toolNode && } - + ); })} @@ -80,20 +96,3 @@ function ToolInputPreview({node}: {node: AITraceSpanNode}) { ); } - -const ToolCallLine = styled(Container)` - &:hover { - opacity: 0.85; - } -`; - -const ClickableTag = styled(Tag)<{hasError?: boolean; isSelected?: boolean}>` - cursor: pointer; - padding: 0 ${p => p.theme.space.xs}; - ${p => - p.isSelected && - ` - outline: 2px solid ${p.hasError ? p.theme.tokens.content.danger : p.theme.tokens.focus.default}; - outline-offset: -2px; - `} -`; diff --git a/static/app/views/explore/conversations/components/toolTags.tsx b/static/app/views/explore/conversations/components/toolTags.tsx index 2e08dfe2785b73..4b572e6359bcaa 100644 --- a/static/app/views/explore/conversations/components/toolTags.tsx +++ b/static/app/views/explore/conversations/components/toolTags.tsx @@ -19,8 +19,9 @@ export function ToolTags({toolNames}: ToolTagsProps) { const [hiddenCount, setHiddenCount] = useState(0); const containerRef = useRef(null); const tagRefs = useRef(new Map()); + const toggleButtonRef = useRef(null); - // Calculate how many tags are hidden (overflow beyond 2 rows) + // Calculate how many tags are hidden (overflow beyond 2 rows, or overlapped by the "+N more" button) useEffect(() => { const container = containerRef.current; if (expanded || !container) { @@ -28,10 +29,19 @@ export function ToolTags({toolNames}: ToolTagsProps) { } const calculateHidden = () => { + const buttonWidth = toggleButtonRef.current?.offsetWidth ?? 0; + const containerWidth = container.offsetWidth; + let hidden = 0; tagRefs.current.forEach(tagEl => { if (tagEl.offsetTop >= TWO_ROW_HEIGHT) { hidden++; + } else if ( + buttonWidth > 0 && + tagEl.offsetLeft + tagEl.offsetWidth > containerWidth - buttonWidth + ) { + // Tag on the last visible row is partially covered by the "+N more" button + hidden++; } }); setHiddenCount(hidden); @@ -45,7 +55,8 @@ export function ToolTags({toolNames}: ToolTagsProps) { cancelAnimationFrame(rafId); observer.disconnect(); }; - }, [toolNames, expanded]); + // hiddenCount is included so we re-check after the button appears (it only renders when hiddenCount > 0) + }, [toolNames, expanded, hiddenCount]); return ( @@ -60,12 +71,13 @@ export function ToolTags({toolNames}: ToolTagsProps) { } }} variant="info" + style={{maxWidth: '100%', minWidth: 0}} > {toolName} ))} {hiddenCount > 0 && !expanded && ( - +