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
79 changes: 79 additions & 0 deletions packages/app/src/components/AISummarizeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Easter egg: April Fools 2026 — see aiSummarize/ for details.
import { useCallback, useEffect, useRef, useState } from 'react';

import AISummaryPanel from './aiSummarize/AISummaryPanel';
import {
dismissEasterEgg,
generateSummary,
isEasterEggVisible,
RowData,
Theme,
} from './aiSummarize';

export default function AISummarizeButton({
rowData,
severityText,
}: {
rowData?: RowData;
severityText?: string;
}) {
const [result, setResult] = useState<{
text: string;
theme: Theme;
} | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [dismissed, setDismissed] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

// Clean up pending timer on unmount.
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);

const handleClick = useCallback(() => {
if (result) {
setIsOpen(prev => !prev);
return;
}
setIsGenerating(true);
setIsOpen(true);
timerRef.current = setTimeout(() => {
setResult(generateSummary(rowData ?? {}, severityText));
setIsGenerating(false);
timerRef.current = null;
}, 1800);
}, [rowData, severityText, result]);

const handleRegenerate = useCallback(() => {
setIsGenerating(true);
timerRef.current = setTimeout(() => {
setResult(generateSummary(rowData ?? {}, severityText));
setIsGenerating(false);
timerRef.current = null;
}, 1200);
}, [rowData, severityText]);

const handleDismiss = useCallback(() => {
dismissEasterEgg();
setIsOpen(false);
// Let Collapse animate closed before unmounting.
setTimeout(() => setDismissed(true), 300);
}, []);

if (dismissed || !isEasterEggVisible()) return null;

return (
<AISummaryPanel
isOpen={isOpen}
isGenerating={isGenerating}
result={result}
onToggle={handleClick}
onRegenerate={handleRegenerate}
onDismiss={handleDismiss}
analyzingLabel="Analyzing event data..."
/>
);
}
140 changes: 140 additions & 0 deletions packages/app/src/components/AISummarizePatternButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Easter egg: April Fools 2026 — see aiSummarize/ for details.
import { useCallback, useEffect, useRef, useState } from 'react';

import {
Pattern,
PATTERN_COLUMN_ALIAS,
SEVERITY_TEXT_COLUMN_ALIAS,
} from '@/hooks/usePatterns';

import AISummaryPanel from './aiSummarize/AISummaryPanel';
import {
dismissEasterEgg,
generatePatternSummary,
isEasterEggVisible,
RowData,
Theme,
} from './aiSummarize';

/**
* Build a synthetic RowData from the first sample event so the summary
* generators can extract OTel facts (service, severity, body, etc.).
*/
function buildRowDataFromSample(
pattern: Pattern,
serviceNameExpression: string,
): { rowData: RowData; severityText?: string } {
const sample = pattern.samples[0];
if (!sample) return { rowData: {} };
return {
rowData: {
__hdx_body: sample[PATTERN_COLUMN_ALIAS],
ServiceName: sample[serviceNameExpression],
__hdx_severity_text: sample[SEVERITY_TEXT_COLUMN_ALIAS],
// Pass through any other fields the sample may have (attributes, etc.)
...sample,
},
severityText: sample[SEVERITY_TEXT_COLUMN_ALIAS],
};
}

export default function AISummarizePatternButton({
pattern,
serviceNameExpression,
}: {
pattern: Pattern;
serviceNameExpression: string;
}) {
const [result, setResult] = useState<{
text: string;
theme: Theme;
} | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [dismissed, setDismissed] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

// Clean up pending timer on unmount.
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);

// Reset when pattern changes.
useEffect(() => {
setResult(null);
setIsOpen(false);
setIsGenerating(false);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}, [pattern]);

const handleClick = useCallback(() => {
if (result) {
setIsOpen(prev => !prev);
return;
}
setIsGenerating(true);
setIsOpen(true);
const { rowData, severityText } = buildRowDataFromSample(
pattern,
serviceNameExpression,
);
timerRef.current = setTimeout(() => {
setResult(
generatePatternSummary(
pattern.pattern,
pattern.count,
rowData,
severityText,
),
);
setIsGenerating(false);
timerRef.current = null;
}, 1800);
}, [pattern, serviceNameExpression, result]);

const handleRegenerate = useCallback(() => {
setIsGenerating(true);
const { rowData, severityText } = buildRowDataFromSample(
pattern,
serviceNameExpression,
);
timerRef.current = setTimeout(() => {
setResult(
generatePatternSummary(
pattern.pattern,
pattern.count,
rowData,
severityText,
),
);
setIsGenerating(false);
timerRef.current = null;
}, 1200);
}, [pattern, serviceNameExpression]);

const handleDismiss = useCallback(() => {
dismissEasterEgg();
setIsOpen(false);
// Let Collapse animate closed before unmounting.
setTimeout(() => setDismissed(true), 300);
}, []);

if (dismissed || !isEasterEggVisible()) return null;

return (
<AISummaryPanel
isOpen={isOpen}
isGenerating={isGenerating}
result={result}
onToggle={handleClick}
onRegenerate={handleRegenerate}
onDismiss={handleDismiss}
analyzingLabel="Analyzing pattern data..."
/>
);
}
1 change: 1 addition & 0 deletions packages/app/src/components/DBRowOverviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export function RowOverviewPanel({
mainContent={mainContent}
mainContentHeader={mainContentColumn}
severityText={firstRow?.__hdx_severity_text}
rowData={firstRow}
/>
</Box>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/components/DBRowSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ const DBRowSidePanel = ({
mainContent={mainContent}
mainContentHeader={mainContentColumn}
severityText={severityText}
rowData={normalizedRow}
breadcrumbPath={breadcrumbPath}
onBreadcrumbClick={handleBreadcrumbClick}
/>
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/components/DBRowSidePanelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FormatTime } from '@/useFormatTime';
import { useUserPreferences } from '@/useUserPreferences';
import { formatDistanceToNowStrictShort } from '@/utils';

import AISummarizeButton from './AISummarizeButton';
import {
DBHighlightedAttributesList,
HighlightedAttribute,
Expand Down Expand Up @@ -131,6 +132,7 @@ export default function DBRowSidePanelHeader({
mainContentHeader,
date,
severityText,
rowData,
breadcrumbPath = [],
onBreadcrumbClick,
}: {
Expand All @@ -139,6 +141,7 @@ export default function DBRowSidePanelHeader({
mainContentHeader?: string;
attributes?: HighlightedAttribute[];
severityText?: string;
rowData?: Record<string, any>;
breadcrumbPath?: BreadcrumbPath;
onBreadcrumbClick?: BreadcrumbNavigationCallback;
}) {
Expand Down Expand Up @@ -272,6 +275,7 @@ export default function DBRowSidePanelHeader({
</Text>
</Paper>
)}
<AISummarizeButton rowData={rowData} severityText={severityText} />
<Box mt="xs">
<DBHighlightedAttributesList attributes={attributes} />
</Box>
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/components/PatternSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { JSDataType } from '@hyperdx/common-utils/dist/clickhouse';
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
import { Button, Card, Drawer, Stack, Text } from '@mantine/core';

// Easter egg: April Fools 2026 — see aiSummarize/ for details.
import AISummarizePatternButton from '@/components/AISummarizePatternButton';
import DBRowSidePanel from '@/components/DBRowSidePanel';
import { RawLogTable } from '@/components/DBRowTable';
import { DrawerBody, DrawerHeader } from '@/components/DrawerUtils';
Expand Down Expand Up @@ -134,6 +136,10 @@ export default function PatternSidePanel({
<Stack>
<Card p="md">
<Text size="sm">{pattern.pattern}</Text>
<AISummarizePatternButton
pattern={pattern}
serviceNameExpression={serviceNameExpression}
/>
</Card>
<Card p="md">
<Card.Section p="md" py="xs">
Expand Down
Loading
Loading