diff --git a/static/app/components/events/autofix/autofixInsightCards.tsx b/static/app/components/events/autofix/autofixInsightCards.tsx index 540cf0caa51297..2d42a0e293b364 100644 --- a/static/app/components/events/autofix/autofixInsightCards.tsx +++ b/static/app/components/events/autofix/autofixInsightCards.tsx @@ -567,7 +567,7 @@ function AutofixInsightCards({ ); } -function useUpdateInsightCard({groupId, runId}: {groupId: string; runId: string}) { +export function useUpdateInsightCard({groupId, runId}: {groupId: string; runId: string}) { const api = useApi({persistInFlight: true}); const queryClient = useQueryClient(); const orgSlug = useOrganization().slug; diff --git a/static/app/components/events/autofix/autofixOutputStream.tsx b/static/app/components/events/autofix/autofixOutputStream.tsx index f0337fe5936a16..f855a771682e3a 100644 --- a/static/app/components/events/autofix/autofixOutputStream.tsx +++ b/static/app/components/events/autofix/autofixOutputStream.tsx @@ -4,15 +4,19 @@ import styled from '@emotion/styled'; import {AnimatePresence, motion} from 'framer-motion'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; -import {SeerLoadingIcon} from 'sentry/components/ai/SeerIcon'; +import {SeerLoadingIcon, SeerWaitingIcon} from 'sentry/components/ai/SeerIcon'; import {Button} from 'sentry/components/core/button'; import {TextArea} from 'sentry/components/core/textarea'; import {Tooltip} from 'sentry/components/core/tooltip'; +import {useUpdateInsightCard} from 'sentry/components/events/autofix/autofixInsightCards'; import {AutofixProgressBar} from 'sentry/components/events/autofix/autofixProgressBar'; import {FlyingLinesEffect} from 'sentry/components/events/autofix/FlyingLinesEffect'; import type {AutofixData} from 'sentry/components/events/autofix/types'; +import {AutofixStepType} from 'sentry/components/events/autofix/types'; import {makeAutofixQueryKey} from 'sentry/components/events/autofix/useAutofix'; import {useTypingAnimation} from 'sentry/components/events/autofix/useTypingAnimation'; +import {getAutofixRunErrorMessage} from 'sentry/components/events/autofix/utils'; +import {IconRefresh} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {singleLineRenderer} from 'sentry/utils/marked/marked'; @@ -102,6 +106,104 @@ interface Props { responseRequired?: boolean; } +interface ActiveLogDisplayProps { + groupId: string; + runId: string; + activeLog?: string; + autofixData?: AutofixData; + isInitializingRun?: boolean; + seerIconRef?: React.RefObject; +} + +function ActiveLogDisplay({ + activeLog = '', + isInitializingRun = false, + seerIconRef, + autofixData, + groupId, + runId, +}: ActiveLogDisplayProps) { + const displayedActiveLog = + useTypingAnimation({ + text: activeLog, + speed: 200, + enabled: !!activeLog, + }) || ''; + + // special case for errored step + const errorMessage = getAutofixRunErrorMessage(autofixData); + const erroredStep = autofixData?.steps?.find(step => step.status === 'ERROR'); + const erroredStepIndex = erroredStep?.index ?? 0; + let retainInsightCardIndex: number | null = null; + if ( + erroredStep && + erroredStep.type === AutofixStepType.DEFAULT && + Array.isArray((erroredStep as any).insights) + ) { + const insights = (erroredStep as any).insights; + if (insights.length > 0) { + retainInsightCardIndex = insights.length; + } + } + + const {mutate: refreshStep, isPending: isRefreshing} = useUpdateInsightCard({ + groupId, + runId, + }); + + if (errorMessage) { + return ( + + + + + {errorMessage} + + + ); + } + if (activeLog) { + return ( + + + + + {seerIconRef?.current && isInitializingRun && ( + + )} + + + + + ); + } + return null; +} + export function AutofixOutputStream({ stream, activeLog = '', @@ -118,12 +220,6 @@ export function AutofixOutputStream({ const isInitializingRun = activeLog === 'Ingesting Sentry data...'; - const displayedActiveLog = useTypingAnimation({ - text: activeLog, - speed: 200, - enabled: !!activeLog, - }); - const orgSlug = useOrganization().slug; const {mutate: send} = useMutation({ @@ -192,27 +288,16 @@ export function AutofixOutputStream({ > - {activeLog && ( - - - - - {seerIconRef.current && isInitializingRun && ( - - )} - - - - - )} + {getAutofixRunErrorMessage(autofixData) || activeLog ? ( + + ) : null} {autofixData && ( @@ -326,7 +411,7 @@ const ActiveLogWrapper = styled('div')` const ActiveLog = styled('div')` flex-grow: 1; word-break: break-word; - margin-top: ${space(0.5)}; + margin-top: ${space(0.25)}; `; const VerticalLine = styled('div')` diff --git a/static/app/components/events/autofix/autofixSteps.tsx b/static/app/components/events/autofix/autofixSteps.tsx index e433f001665ad0..c41466f5397b02 100644 --- a/static/app/components/events/autofix/autofixSteps.tsx +++ b/static/app/components/events/autofix/autofixSteps.tsx @@ -13,11 +13,9 @@ import {AutofixSolution} from 'sentry/components/events/autofix/autofixSolution' import { type AutofixData, type AutofixProgressItem, - AutofixStatus, type AutofixStep, AutofixStepType, } from 'sentry/components/events/autofix/types'; -import {IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import testableTransition from 'sentry/utils/testableTransition'; @@ -150,55 +148,6 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) { return null; } - if (data.status === AutofixStatus.ERROR) { - const errorStep = steps.find(step => step.status === AutofixStatus.ERROR); - const errorMessage = errorStep?.completedMessage || t('Something went wrong.'); - - // sugar coat common errors - let customErrorMessage = ''; - if ( - errorMessage.toLowerCase().includes('overloaded') || - errorMessage.toLowerCase().includes('no completion tokens') || - errorMessage.toLowerCase().includes('exhausted') - ) { - customErrorMessage = t( - 'The robots are having a moment. Our LLM provider is overloaded - please try again soon.' - ); - } else if ( - errorMessage.toLowerCase().includes('prompt') || - errorMessage.toLowerCase().includes('tokens') - ) { - customErrorMessage = t( - "Seer worked so hard that it couldn't fit all its findings in its own memory. Please try again." - ); - } else if (errorMessage.toLowerCase().includes('iterations')) { - customErrorMessage = t( - 'Seer was taking a ton of iterations, so we pulled the plug out of fear it might go rogue. Please try again.' - ); - } else if (errorMessage.toLowerCase().includes('timeout')) { - customErrorMessage = t( - 'Seer was taking way too long, so we pulled the plug to turn it off and on again. Please try again.' - ); - } else { - customErrorMessage = t( - "Oops, Seer went kaput. We've dispatched Seer to fix Seer. In the meantime, try again?" - ); - } - - return ( - - - - {customErrorMessage || ( - - {t('Something went wrong with Autofix:')} {errorMessage} - - )} - - - ); - } - const lastStep = steps[steps.length - 1]; const logs: AutofixProgressItem[] = lastStep!.progress?.filter(isProgressLog) ?? []; const activeLog = @@ -209,7 +158,7 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) { const isInitialMount = !isMountedRef.current; return ( - +
{steps.map((step, index) => { const previousDefaultStepIndex = steps .slice(0, index) @@ -283,7 +232,7 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) { autofixData={data} /> )} - +
); } @@ -296,26 +245,6 @@ const StepMessage = styled('div')` text-align: left; `; -const ErrorMessage = styled('div')` - font-size: ${p => p.theme.fontSizeMedium}; - color: ${p => p.theme.subText}; -`; - -const StepsContainer = styled('div')``; - -const ErrorContainer = styled('div')` - margin-top: ${space(1)}; - display: flex; - align-items: center; - flex-direction: column; - gap: ${space(1)}; -`; - -const StyledArrow = styled(IconArrow)` - color: ${p => p.theme.subText}; - opacity: 0.5; -`; - const StepCard = styled('div')` overflow: hidden; diff --git a/static/app/components/events/autofix/utils.tsx b/static/app/components/events/autofix/utils.tsx index 62fee2b04a83cf..7f51c12d271428 100644 --- a/static/app/components/events/autofix/utils.tsx +++ b/static/app/components/events/autofix/utils.tsx @@ -7,6 +7,7 @@ import { AutofixStatus, AutofixStepType, } from 'sentry/components/events/autofix/types'; +import {t} from 'sentry/locale'; import type {Group} from 'sentry/types/group'; export function getRootCauseDescription(autofixData: AutofixData) { @@ -187,3 +188,44 @@ export function getAutofixRunExists(group: Group) { export function isIssueQuickFixable(group: Group) { return group.seerFixabilityScore && group.seerFixabilityScore > 0.7; } + +export function getAutofixRunErrorMessage(autofixData: AutofixData | undefined) { + if (!autofixData || autofixData.status !== AutofixStatus.ERROR) { + return null; + } + + const errorStep = autofixData.steps?.find(step => step.status === AutofixStatus.ERROR); + const errorMessage = errorStep?.completedMessage || t('Something went wrong.'); + + let customErrorMessage = ''; + if ( + errorMessage.toLowerCase().includes('overloaded') || + errorMessage.toLowerCase().includes('no completion tokens') || + errorMessage.toLowerCase().includes('exhausted') + ) { + customErrorMessage = t( + 'The robots are having a moment. Our LLM provider is overloaded - please try again soon.' + ); + } else if ( + errorMessage.toLowerCase().includes('prompt') || + errorMessage.toLowerCase().includes('tokens') + ) { + customErrorMessage = t( + "Seer worked so hard that it couldn't fit all its findings in its own memory. Please try again." + ); + } else if (errorMessage.toLowerCase().includes('iterations')) { + customErrorMessage = t( + 'Seer was taking a ton of iterations, so we pulled the plug out of fear it might go rogue. Please try again.' + ); + } else if (errorMessage.toLowerCase().includes('timeout')) { + customErrorMessage = t( + 'Seer was taking way too long, so we pulled the plug to turn it off and on again. Please try again.' + ); + } else { + customErrorMessage = t( + "Oops, Seer went kaput. We've dispatched Seer to fix Seer. In the meantime, try again?" + ); + } + + return customErrorMessage; +}