diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 0241a1762a90..297c1b6c9402 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -1,5 +1,6 @@ import {type Key, useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; +import * as qs from 'query-string'; import {LinkButton} from '@sentry/scraps/button'; import {Container, Flex} from '@sentry/scraps/layout'; @@ -15,8 +16,16 @@ import { ConversationSplitLayout, ConversationViewSkeleton, } from 'sentry/views/explore/conversations/components/conversationLayout'; +import { + ConversationSpanDetail, + type DetailTab, +} from 'sentry/views/explore/conversations/components/conversationSpanDetail'; import {ConversationAggregatesBar} from 'sentry/views/explore/conversations/components/conversationSummary'; import {MessagesPanel} from 'sentry/views/explore/conversations/components/messagesPanel'; +import { + MessagesPanelNew, + MessagesPanelSkeleton, +} from 'sentry/views/explore/conversations/components/messagesPanelNew'; import {useConversation} from 'sentry/views/explore/conversations/hooks/useConversation'; import {useConversationSelection} from 'sentry/views/explore/conversations/hooks/useConversationSelection'; import {CONVERSATIONS_LANDING_SUB_PATH} from 'sentry/views/explore/conversations/settings'; @@ -39,6 +48,7 @@ export function TraceAiConversations({ traceSlug, }: TraceAiConversationsProps) { const organization = useOrganization(); + const hasRedesign = hasGenAiConversationsRedesignFeature(organization); const [activeSubTab, setActiveSubTab] = useState('spans'); const [selectedSpanId, setSelectedSpanId] = useState(null); @@ -72,34 +82,40 @@ export function TraceAiConversations({ [conversationNodes, nodeTraceMap, traceSlug] ); - const hasRedesign = hasGenAiConversationsRedesignFeature(organization); + const tabItems = useMemo((): Array<{ + conversationId: string | null; + key: string; + label: string; + }> => { + const spansTab = { + key: 'spans', + label: hasRedesign ? t('Timeline') : t('Spans'), + conversationId: null, + }; + const conversationTabs = conversationIds.map(id => ({ + key: `chat-${id}`, + label: hasRedesign + ? conversationIds.length === 1 + ? t('Transcript') + : t('Transcript %s', id.slice(0, 8)) + : conversationIds.length === 1 + ? t('Chat') + : t('Chat %s', id.slice(0, 8)), + conversationId: id, + })); - const tabItems = useMemo( - (): Array<{conversationId: string | null; key: string; label: string}> => [ - { - key: 'spans', - label: hasRedesign ? t('Timeline') : t('Spans'), - conversationId: null, - }, - ...conversationIds.map(id => ({ - key: `chat-${id}`, - label: hasRedesign - ? conversationIds.length === 1 - ? t('Transcript') - : t('Transcript %s', id.slice(0, 8)) - : conversationIds.length === 1 - ? t('Chat') - : t('Chat %s', id.slice(0, 8)), - conversationId: id, - })), - ], - [conversationIds, hasRedesign] - ); + return [spansTab, ...conversationTabs]; + }, [conversationIds, hasRedesign]); const linkConversationId = activeConversationId ?? conversationIds[0] ?? null; const conversationUrl = linkConversationId ? normalizeUrl( - `/organizations/${organization.slug}/explore/${CONVERSATIONS_LANDING_SUB_PATH}/${linkConversationId}/${selectedSpanId && activeConversationId ? `?spanId=${encodeURIComponent(selectedSpanId)}` : ''}` + `/organizations/${organization.slug}/explore/${CONVERSATIONS_LANDING_SUB_PATH}/${linkConversationId}/?${qs.stringify( + { + referrer: 'trace-view', + ...(selectedSpanId && activeConversationId ? {spanId: selectedSpanId} : {}), + } + )}` ) : null; @@ -134,14 +150,25 @@ export function TraceAiConversations({ {tabItems.map(item => item.conversationId ? ( - + {hasRedesign ? ( + + ) : ( + + )} ) : ( @@ -240,6 +267,81 @@ function TraceConversationChat({ ); } +function TraceConversationTranscript({ + nodes, + nodeTraceMap, + isLoading, + error, + selectedSpanId, + onSelectSpan, +}: { + error: boolean; + isLoading: boolean; + nodeTraceMap: Map; + nodes: AITraceSpanNode[]; + onSelectSpan: (spanId: string) => void; + selectedSpanId: string | null; +}) { + const {selectedNode, handleSelectNode} = useConversationSelection({ + nodes, + selectedSpanId, + onSelectSpan, + isLoading, + autoSelectDefaultNode: false, + }); + + const [detailTab, setDetailTab] = useState('input'); + + if (isLoading) { + return ; + } + + if (error) { + return {t('Failed to load conversation')}; + } + + if (nodes.length === 0) { + return ( + + {t('No chat messages in this portion of the conversation')} + + ); + } + + return ( + + + + + + + } + right={ + selectedNode ? ( + + ) : ( + {t('Select a span to see its details')} + ) + } + /> + + ); +} + const StyledTabs = styled(Tabs)` min-height: 0; flex: 1;