Skip to content

Commit 5659a0e

Browse files
Fix event typing render (#1465)
* Fix event typing render Make not started agent nodes look the same as skipped ones Remove node kebab menu on Incident template chart
1 parent 602c303 commit 5659a0e

File tree

9 files changed

+227
-73
lines changed

9 files changed

+227
-73
lines changed

.storybook/preview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { store } from "./store";
1414

1515
const preview: Preview = {
1616
decorators: [
17-
withRouter,
17+
withRouter, // TODO: Fix the story crash on frequent rerendering
1818
(Story: StoryFn, context): JSX.Element => {
1919
const [isInitialized, setIsInitialized] = useState(false);
2020
const theme = context.globals.theme as Theme;

src/components/Agentic/IncidentDetails/IncidentSummary/index.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import { IncidentSummaryRecord } from "./IncidentSummaryRecord";
22
import * as s from "./styles";
33
import type { IncidentSummaryProps } from "./types";
44

5-
export const IncidentSummary = ({ records }: IncidentSummaryProps) => (
6-
<s.Container>
7-
{records.map((record) => (
8-
<IncidentSummaryRecord
9-
key={record.agent}
10-
agent={record.agent_display_name}
11-
datetime={record.timestamp}
12-
text={record.text}
13-
/>
14-
))}
15-
</s.Container>
16-
);
5+
export const IncidentSummary = ({ records }: IncidentSummaryProps) => {
6+
if (records.length === 0) {
7+
return null;
8+
}
9+
10+
return (
11+
<s.Container>
12+
{records.map((record) => (
13+
<IncidentSummaryRecord
14+
key={record.agent}
15+
agent={record.agent_display_name}
16+
datetime={record.timestamp}
17+
text={record.text}
18+
/>
19+
))}
20+
</s.Container>
21+
);
22+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { Meta, StoryObj } from "@storybook/react-webpack5";
2+
import { useEffect, useState } from "react";
3+
import { fn } from "storybook/test";
4+
import { AgentChat } from ".";
5+
import type { IncidentAgentEvent } from "../../../../redux/services/types";
6+
7+
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
8+
const meta: Meta<typeof AgentChat> = {
9+
title: "Agentic/common/AgentChat",
10+
component: AgentChat,
11+
parameters: {
12+
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
13+
layout: "fullscreen"
14+
}
15+
};
16+
17+
export default meta;
18+
19+
type Story = StoryObj<typeof AgentChat>;
20+
21+
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
22+
const mockedAgentEvents: IncidentAgentEvent[] = [
23+
{
24+
id: "1",
25+
type: "human",
26+
message: "Can you help me understand why my API response time is slow?",
27+
agent_name: "user"
28+
},
29+
{
30+
id: "2",
31+
type: "token",
32+
message: "Let me analyze your application's performance data...",
33+
agent_name: "agent"
34+
},
35+
{
36+
id: "3",
37+
type: "token",
38+
message:
39+
"I've found several performance issues in your application:\n\n1. **Database Query Optimization**: Your user lookup queries are taking an average of 2.3 seconds\n2. **Memory Usage**: High memory allocation in the user service\n3. **Cache Misses**: 78% cache miss rate on user data\n\nWould you like me to suggest specific optimizations for any of these areas?",
40+
agent_name: "agent"
41+
},
42+
{
43+
id: "4",
44+
type: "tool",
45+
agent_name: "agent",
46+
message:
47+
'\n```json\n{\n "success": false,\n "blockers": "Limited tool access prevents thorough investigation of system-wide issues, infrastructure problems, service mesh configurations, and external dependencies. Need access to Kubernetes API, service mesh telemetry, network monitoring tools, and container logs.",\n "result": {\n "is_relevant": true,\n "objective_success": false,\n "blockers": "Limited tool access prevents thorough investigation of system-wide issues, infrastructure problems, service mesh configurations, and external dependencies. Need access to Kubernetes API, service mesh telemetry, network monitoring tools, and container logs.",\n "beyond_the_result": {\n "summary": "Unable to fully investigate alternative causes due to tool limitations, but analysis suggests potential issues in service mesh, network policies, or external dependencies",\n "description": "The investigation revealed a severe performance degradation (2650%) in the PipelineConnector Execute operation that has been ongoing for over 24 hours. While direct investigation was limited by tool access, the pattern and severity suggest potential issues with service mesh routing, network policies, cross-namespace communication, or external service dependencies rather than simple resource constraints.",\n "confidence_level": "30",\n "confidence_level_reason": "Limited tool access prevents thorough investigation of infrastructure and network-related causes. The assessment is based primarily on timing patterns and service impact analysis rather than direct evidence."\n },\n "next_steps_suggestions": "1. Request access to Kubernetes cluster information and API\\n2. Obtain access to service mesh telemetry and dashboard\\n3. Deploy network monitoring tools\\n4. Enable access to container logs\\n5. Once access is granted, conduct thorough analysis of namespace configurations, service mesh settings, network policies, and external service dependencies",\n "actions_taken": [\n {\n "action": "Gathered relevant objects",\n "action_execution_success": true,\n "action_command": "list_relevant_incident_objects",\n "resolution_success_status": "PARTIAL",\n "resolution_explanation": "Successfully identified the critical trace ID but couldn\'t gather infrastructure-related objects",\n "resolution_success_evidence": "Retrieved trace ID FB0C56FA98816BBBFBB934CCEDEA72E4 showing the performance degradation",\n "state_changes_confirmed_due_to_actions": "Confirmed existence of trace showing 2650% performance degradation in PipelineConnector Execute operation"\n },\n {\n "action": "Tracked relevant trace",\n "action_execution_success": true,\n "action_command": "track_incident_relevant_object",\n "resolution_success_status": "PARTIAL",\n "resolution_explanation": "Successfully tracked the critical trace ID for future reference",\n "resolution_success_evidence": "Trace ID FB0C56FA98816BBBFBB934CCEDEA72E4 was successfully tracked",\n "state_changes_confirmed_due_to_actions": "Added trace to tracked objects for future investigation"\n }\n ]\n }\n}\n```\n',
48+
tool_name: "kubernetes_resolution_expert_tool",
49+
mcp_name: "",
50+
status: "success"
51+
},
52+
{
53+
id: "5",
54+
type: "token",
55+
message:
56+
"Here are my recommendations for optimizing your database queries:\n\n```sql\n-- Add an index on the email column\nCREATE INDEX idx_users_email ON users(email);\n\n-- Use prepared statements\nSELECT id, name, email FROM users WHERE email = ?\n```\n\nThis should reduce your query time from 2.3s to under 100ms.",
57+
agent_name: "agent"
58+
}
59+
];
60+
61+
const EVENTS_CURSOR = 2;
62+
const EVENTS_TIMEOUT = 2000;
63+
64+
export const Default: Story = {
65+
args: {
66+
data: mockedAgentEvents.slice(0, EVENTS_CURSOR),
67+
isDataLoading: false,
68+
onMessageSend: fn(),
69+
typeInitialMessages: false
70+
},
71+
render: (args) => {
72+
const [events, setEvents] = useState<IncidentAgentEvent[]>(args.data ?? []);
73+
74+
useEffect(() => {
75+
// Simulate messages arriving progressively
76+
const timeouts: number[] = [];
77+
78+
mockedAgentEvents.slice(EVENTS_CURSOR).forEach((event, index) => {
79+
const timeout = window.setTimeout(() => {
80+
setEvents((prev) => [...prev, event]);
81+
}, index * EVENTS_TIMEOUT);
82+
83+
timeouts.push(timeout);
84+
});
85+
86+
return () => {
87+
timeouts.forEach((timeout) => clearTimeout(timeout));
88+
};
89+
}, []);
90+
91+
return <AgentChat {...args} data={events} />;
92+
}
93+
};

src/components/Agentic/common/AgentEventsList/index.tsx

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,101 @@
11
import { useEffect, useMemo, useState } from "react";
2-
import { isNumber } from "../../../../typeGuards/isNumber";
2+
import type { IncidentAgentEvent } from "../../../../redux/services/types";
33
import { AgentEvent } from "./AgentEvent";
4-
import type { AgentEventsListProps } from "./types";
4+
import type { AgentEventsListProps, RenderState } from "./types";
5+
6+
const isTypingEvent = (event: IncidentAgentEvent) =>
7+
["ai", "token"].includes(event.type);
58

69
export const AgentEventsList = ({
710
events,
811
onNavigateToIncident,
912
typeInitialEvents
1013
}: AgentEventsListProps) => {
11-
const [initialEventsCount] = useState<number>(
12-
typeInitialEvents ? 0 : events.length
13-
);
14-
const [eventsVisibleCount, setEventsVisibleCount] = useState<number>(
14+
const [initialVisibleCount] = useState(() =>
1515
typeInitialEvents ? 0 : events.length
1616
);
1717

18-
const agentEventsIndexes = useMemo(
19-
() =>
20-
events.reduce((acc, event, index) => {
21-
if (["ai", "token"].includes(event.type)) {
22-
acc.push(index);
23-
}
24-
return acc;
25-
}, [] as number[]),
26-
[events]
27-
);
18+
const [renderState, setRenderState] = useState<RenderState>({
19+
currentEventIndex: initialVisibleCount - 1,
20+
isTyping: false
21+
});
2822

2923
const handleEventTypingComplete = (id: string) => {
30-
const i = events.findIndex((x) => x.id === id);
24+
const completedEventIndex = events.findIndex((event) => event.id === id);
3125

32-
const nextAgentEventIndex = agentEventsIndexes.find((el) => el > i);
26+
const nextTypingEventIndex = events.findIndex(
27+
(event, index) => index > completedEventIndex && isTypingEvent(event)
28+
);
3329

34-
if (isNumber(nextAgentEventIndex)) {
35-
setEventsVisibleCount(nextAgentEventIndex + 1);
30+
if (nextTypingEventIndex !== -1) {
31+
setRenderState({
32+
currentEventIndex: nextTypingEventIndex,
33+
isTyping: true
34+
});
3635
} else {
37-
setEventsVisibleCount(events.length);
36+
setRenderState({
37+
currentEventIndex: events.length - 1,
38+
isTyping: false
39+
});
3840
}
3941
};
4042

41-
const visibleEvents = useMemo(
42-
() => events.slice(0, eventsVisibleCount),
43-
[events, eventsVisibleCount]
44-
);
45-
4643
const shouldShowTypingForEvent = (id: string) => {
47-
const index = visibleEvents.findIndex((x) => x.id === id);
44+
const eventIndex = events.findIndex((event) => event.id === id);
45+
return (
46+
eventIndex >= initialVisibleCount &&
47+
eventIndex === renderState.currentEventIndex &&
48+
renderState.isTyping
49+
);
50+
};
4851

49-
if (index < 0) {
50-
return false;
52+
// Handle new events
53+
useEffect(() => {
54+
if (renderState.currentEventIndex >= events.length - 1) {
55+
return;
5156
}
5257

53-
return index >= initialEventsCount;
54-
};
58+
if (renderState.isTyping) {
59+
return;
60+
}
5561

56-
useEffect(() => {
57-
if (events.length > eventsVisibleCount) {
58-
const nextAgentEventIndex = agentEventsIndexes.find(
59-
(index) => index >= eventsVisibleCount
60-
);
62+
const nextEventIndex = renderState.currentEventIndex + 1;
63+
const nextEvent = events[nextEventIndex];
64+
65+
const isInitialEvent = nextEventIndex < initialVisibleCount;
6166

62-
if (isNumber(nextAgentEventIndex)) {
63-
setEventsVisibleCount(nextAgentEventIndex + 1);
64-
} else {
65-
setEventsVisibleCount(events.length);
66-
}
67+
// Start typing if
68+
// either it's an initial event with typeInitialEvents=true
69+
// or
70+
// it's a new event
71+
if (
72+
isTypingEvent(nextEvent) &&
73+
((isInitialEvent && typeInitialEvents) || !isInitialEvent)
74+
) {
75+
setRenderState({
76+
currentEventIndex: nextEventIndex,
77+
isTyping: true
78+
});
79+
return;
6780
}
68-
}, [events.length, eventsVisibleCount, agentEventsIndexes]);
81+
82+
// Otherwise, show the next event immediately
83+
setRenderState((prev) => ({
84+
...prev,
85+
currentEventIndex: nextEventIndex
86+
}));
87+
}, [
88+
events,
89+
renderState.currentEventIndex,
90+
renderState.isTyping,
91+
initialVisibleCount,
92+
typeInitialEvents
93+
]);
94+
95+
const visibleEvents = useMemo(
96+
() => events.slice(0, renderState.currentEventIndex + 1),
97+
[events, renderState.currentEventIndex]
98+
);
6999

70100
return visibleEvents.map((event) => (
71101
<AgentEvent

src/components/Agentic/common/AgentEventsList/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export interface AgentEventsListProps {
55
onNavigateToIncident?: () => void;
66
typeInitialEvents?: boolean;
77
}
8+
9+
export interface RenderState {
10+
currentEventIndex: number;
11+
isTyping: boolean;
12+
}

src/components/Agentic/common/AgentFlowChart/index.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,28 @@ import type {
1010
import { AgentFlowChartNodeToolbar } from "./AgentFlowChartNodeToolbar";
1111
import type { AgentFlowChartProps, ExtendedAgent } from "./types";
1212

13+
const isAgentWaitingOrSkipped = (
14+
agents: ExtendedAgent[],
15+
agentName: string
16+
) => {
17+
const agent = agents.find((a) => a.name === agentName);
18+
return agent ? ["waiting", "skipped"].includes(agent.status) : undefined;
19+
};
20+
1321
const getFlowChartNodeData = ({
1422
agent,
1523
isSelected,
1624
isInteractive,
25+
isDisabled,
1726
isEditMode,
1827
onAddMCPServer,
1928
onEditMCPServer,
2029
onDeleteMCPServer
2130
}: {
2231
agent?: ExtendedAgent;
23-
isInteractive?: boolean;
2432
isSelected?: boolean;
33+
isInteractive?: boolean;
34+
isDisabled?: boolean;
2535
isEditMode?: boolean;
2636
onAddMCPServer?: (agentName: string) => void;
2737
onEditMCPServer?: (agentName: string, server: string) => void;
@@ -69,7 +79,7 @@ const getFlowChartNodeData = ({
6979
isPending: agent.status === "pending",
7080
hasError: agent.status === "error",
7181
isInteractive,
72-
isDisabled: agent.status === "skipped",
82+
isDisabled,
7383
sideContainers: [
7484
{
7585
isVisible: Boolean(agent.mcp_servers.length > 0 || isEditMode),
@@ -86,8 +96,7 @@ const getFlowChartNodeData = ({
8696
/>
8797
)
8898
}
89-
],
90-
isKebabMenuVisible: isEditMode
99+
]
91100
}
92101
: {};
93102
};
@@ -186,8 +195,11 @@ export const AgentFlowChartComponent = ({
186195
agent: extendedAgents?.find((a) => a.name === "watchman"),
187196
isSelected: "watchman" === selectedAgentId,
188197
isInteractive:
189-
extendedAgents?.find((a) => a.name === "watchman")?.status !==
190-
"skipped",
198+
Boolean(isEditMode) ||
199+
!isAgentWaitingOrSkipped(extendedAgents, "watchman"),
200+
isDisabled:
201+
!isEditMode &&
202+
isAgentWaitingOrSkipped(extendedAgents, "watchman"),
191203
isEditMode,
192204
onAddMCPServer,
193205
onEditMCPServer,
@@ -204,8 +216,10 @@ export const AgentFlowChartComponent = ({
204216
agent: extendedAgents?.find((a) => a.name === "triager"),
205217
isSelected: "triager" === selectedAgentId,
206218
isInteractive:
207-
extendedAgents?.find((a) => a.name === "triager")?.status !==
208-
"skipped",
219+
Boolean(isEditMode) ||
220+
!isAgentWaitingOrSkipped(extendedAgents, "triager"),
221+
isDisabled:
222+
!isEditMode && isAgentWaitingOrSkipped(extendedAgents, "triager"),
209223
isEditMode,
210224
onAddMCPServer,
211225
onEditMCPServer,
@@ -222,8 +236,11 @@ export const AgentFlowChartComponent = ({
222236
agent: extendedAgents?.find((a) => a.name === "infra_resolver"),
223237
isSelected: "infra_resolver" === selectedAgentId,
224238
isInteractive:
225-
extendedAgents?.find((a) => a.name === "infra_resolver")
226-
?.status !== "skipped",
239+
Boolean(isEditMode) ||
240+
!isAgentWaitingOrSkipped(extendedAgents, "infra_resolver"),
241+
isDisabled:
242+
!isEditMode &&
243+
isAgentWaitingOrSkipped(extendedAgents, "infra_resolver"),
227244
isEditMode,
228245
onAddMCPServer,
229246
onEditMCPServer,
@@ -240,8 +257,11 @@ export const AgentFlowChartComponent = ({
240257
agent: extendedAgents?.find((a) => a.name === "code_resolver"),
241258
isSelected: "code_resolver" === selectedAgentId,
242259
isInteractive:
243-
extendedAgents?.find((a) => a.name === "code_resolver")
244-
?.status !== "skipped",
260+
Boolean(isEditMode) ||
261+
!isAgentWaitingOrSkipped(extendedAgents, "code_resolver"),
262+
isDisabled:
263+
!isEditMode &&
264+
isAgentWaitingOrSkipped(extendedAgents, "code_resolver"),
245265
isEditMode,
246266
onAddMCPServer,
247267
onEditMCPServer,

0 commit comments

Comments
 (0)