Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
143 changes: 114 additions & 29 deletions static/app/components/events/autofix/autofixOutputStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -102,6 +106,104 @@ interface Props {
responseRequired?: boolean;
}

interface ActiveLogDisplayProps {
groupId: string;
runId: string;
activeLog?: string;
autofixData?: AutofixData;
isInitializingRun?: boolean;
seerIconRef?: React.RefObject<HTMLDivElement | null>;
}

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 (
<ActiveLogWrapper>
<SeerIconContainer>
<SeerWaitingIcon size="lg" />
</SeerIconContainer>
<ActiveLog>{errorMessage}</ActiveLog>
<Button
size="xs"
borderless
aria-label={t('Retry step')}
title={t('Retry step')}
onClick={() =>
refreshStep({
message: '',
step_index: erroredStepIndex,
retain_insight_card_index: retainInsightCardIndex,
})
}
disabled={isRefreshing}
style={{marginLeft: 'auto'}}
>
<IconRefresh size="sm" />
</Button>
</ActiveLogWrapper>
);
}
if (activeLog) {
return (
<ActiveLogWrapper>
<Tooltip
title={t(
"Seer is hard at work. Feel free to leave - it'll keep running in the background."
)}
>
<SeerIconContainer ref={seerIconRef}>
<StyledAnimatedSeerIcon size="lg" />
{seerIconRef?.current && isInitializingRun && (
<FlyingLinesEffect targetElement={seerIconRef.current} />
)}
</SeerIconContainer>
</Tooltip>
<ActiveLog
dangerouslySetInnerHTML={{
__html: singleLineRenderer(displayedActiveLog),
}}
/>
</ActiveLogWrapper>
);
}
return null;
}

export function AutofixOutputStream({
stream,
activeLog = '',
Expand All @@ -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({
Expand Down Expand Up @@ -192,27 +288,16 @@ export function AutofixOutputStream({
>
<VerticalLine />
<Container required={responseRequired}>
{activeLog && (
<ActiveLogWrapper>
<Tooltip
title={t(
"Seer is hard at work. Feel free to leave - it'll keep running in the background."
)}
>
<SeerIconContainer ref={seerIconRef}>
<StyledAnimatedSeerIcon size="lg" />
{seerIconRef.current && isInitializingRun && (
<FlyingLinesEffect targetElement={seerIconRef.current} />
)}
</SeerIconContainer>
</Tooltip>
<ActiveLog
dangerouslySetInnerHTML={{
__html: singleLineRenderer(displayedActiveLog),
}}
/>
</ActiveLogWrapper>
)}
{getAutofixRunErrorMessage(autofixData) || activeLog ? (
<ActiveLogDisplay
activeLog={activeLog}
isInitializingRun={isInitializingRun}
seerIconRef={seerIconRef}
autofixData={autofixData}
groupId={groupId}
runId={runId}
/>
) : null}
{autofixData && (
<ProgressBarWrapper>
<AutofixProgressBar autofixData={autofixData} />
Expand Down Expand Up @@ -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')`
Expand Down
75 changes: 2 additions & 73 deletions static/app/components/events/autofix/autofixSteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 (
<ErrorContainer>
<StyledArrow direction="down" size="sm" />
<ErrorMessage>
{customErrorMessage || (
<Fragment>
{t('Something went wrong with Autofix:')} {errorMessage}
</Fragment>
)}
</ErrorMessage>
</ErrorContainer>
);
}

const lastStep = steps[steps.length - 1];
const logs: AutofixProgressItem[] = lastStep!.progress?.filter(isProgressLog) ?? [];
const activeLog =
Expand All @@ -209,7 +158,7 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) {
const isInitialMount = !isMountedRef.current;

return (
<StepsContainer>
<div>
{steps.map((step, index) => {
const previousDefaultStepIndex = steps
.slice(0, index)
Expand Down Expand Up @@ -283,7 +232,7 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) {
autofixData={data}
/>
)}
</StepsContainer>
</div>
);
}

Expand All @@ -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;

Expand Down
42 changes: 42 additions & 0 deletions static/app/components/events/autofix/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Loading