Skip to content

Commit 2a17896

Browse files
authored
Update step settings AI chat to display step metadata (#1650)
Fixes OPS-3103 Approved by Olga ## Additional Notes <img width="1480" height="817" alt="Screenshot 2025-11-24 at 2 40 40 PM" src="https://github.com/user-attachments/assets/9ada55e1-050f-4488-a2a9-1f81b30deabd" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * AI assistant now shows step context (logo, step name, block display name and 1-based position) when opened from step settings. * **Improvements** * Assistant title changed to "Generate with AI". * Top bar and chat layout reorganized for clearer history/new-chat/close actions and step info. * Docked chat height now adapts to available parent height for better fit. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 6295454 commit 2a17896

6 files changed

Lines changed: 426 additions & 74 deletions

File tree

packages/react-ui/src/app/features/builder/ai-chat/step-settings-assistant-ui-chat.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { useTheme } from '@/app/common/providers/theme-provider';
2+
import { blocksHooks } from '@/app/features/blocks/lib/blocks-hook';
23
import {
34
AI_CHAT_CONTAINER_SIZES,
45
AiCliChatContainerSizeState,
56
cn,
67
StepSettingsAssistantUiChatContainer,
78
} from '@openops/components/ui';
8-
import { FlowVersion } from '@openops/shared';
9-
import { useCallback } from 'react';
9+
import { flowHelper, FlowVersion } from '@openops/shared';
10+
import { useCallback, useMemo } from 'react';
1011
import { useAiModelSelector } from '../../ai/lib/ai-model-selector-hook';
1112
import { useNetworkStatusWithWarning } from '../../ai/lib/hooks/use-network-status-with-warning';
1213
import { useStepSettingsAssistantChat } from '../assistant-ui/hooks/use-step-settings-assistant-chat';
@@ -81,6 +82,16 @@ const StepSettingsAssistantUiChat = ({
8182
const { isShowingSlowWarning, connectionError } =
8283
useNetworkStatusWithWarning(chatStatus);
8384

85+
const { step, stepIndex } = useMemo(
86+
() => flowHelper.getStepWithIndex(flowVersion, selectedStep),
87+
[flowVersion, selectedStep],
88+
);
89+
90+
const { stepMetadata } = blocksHooks.useStepMetadata({
91+
step: step,
92+
enabled: !!step,
93+
});
94+
8495
return (
8596
<StepSettingsAssistantUiChatContainer
8697
parentHeight={middlePanelSize.height}
@@ -107,6 +118,10 @@ const StepSettingsAssistantUiChat = ({
107118
})}
108119
isShowingSlowWarning={isShowingSlowWarning}
109120
connectionError={connectionError}
121+
stepLogoUrl={stepMetadata?.logoUrl}
122+
stepDisplayName={step?.displayName}
123+
stepIndex={stepIndex}
124+
blockDisplayName={stepMetadata?.displayName}
110125
></StepSettingsAssistantUiChatContainer>
111126
);
112127
};

packages/shared/src/lib/flows/flow-helper.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,25 @@ function getStep(
336336
);
337337
}
338338

339+
function getStepWithIndex(
340+
flowVersion: FlowVersion,
341+
stepName: string,
342+
): {
343+
step: Action | TriggerWithOptionalId | undefined;
344+
stepIndex: number | undefined;
345+
} {
346+
const step = getStep(flowVersion, stepName);
347+
348+
if (!step) {
349+
return { step: undefined, stepIndex: undefined };
350+
}
351+
352+
const steps = getAllSteps(flowVersion.trigger);
353+
const stepIndex = steps.findIndex((s) => s.name === step.name) + 1;
354+
355+
return { step, stepIndex };
356+
}
357+
339358
function getSplitBranches(step: SplitActionSchema): SplitBranch[] {
340359
return step.settings?.options.map((option) => ({
341360
optionId: option.id,
@@ -1295,6 +1314,7 @@ export const flowHelper = {
12951314
},
12961315

12971316
getStep,
1317+
getStepWithIndex,
12981318
isAction,
12991319
isTrigger,
13001320
getAllSteps,

packages/shared/src/lib/flows/test/flow-helper.test.ts

Lines changed: 251 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { Action } from '../actions/action';
1+
import { Action, ActionType } from '../actions/action';
22
import { flowHelper } from '../flow-helper';
3+
import { FlowVersion, FlowVersionState } from '../flow-version';
4+
import { Trigger, TriggerType } from '../triggers/trigger';
35

46
describe('flowHelper', () => {
57
describe('indexToAlphabetical', () => {
@@ -116,4 +118,252 @@ describe('flowHelper', () => {
116118
});
117119
});
118120
});
121+
122+
describe('getStepWithIndex', () => {
123+
const createSimpleFlowVersion = (): FlowVersion => {
124+
const trigger: Trigger = {
125+
id: 'trigger_id',
126+
name: 'trigger',
127+
type: TriggerType.EMPTY,
128+
valid: true,
129+
settings: {},
130+
displayName: 'Trigger',
131+
nextAction: {
132+
id: 'action_1',
133+
name: 'action_1',
134+
type: ActionType.CODE,
135+
valid: true,
136+
settings: {
137+
sourceCode: {
138+
code: 'test',
139+
packageJson: '{}',
140+
},
141+
input: {},
142+
},
143+
displayName: 'Action 1',
144+
nextAction: {
145+
id: 'action_2',
146+
name: 'action_2',
147+
type: ActionType.CODE,
148+
valid: true,
149+
settings: {
150+
sourceCode: {
151+
code: 'test',
152+
packageJson: '{}',
153+
},
154+
input: {},
155+
},
156+
displayName: 'Action 2',
157+
nextAction: undefined,
158+
},
159+
},
160+
};
161+
162+
return {
163+
id: 'flow_version_id',
164+
flowId: 'flow_id',
165+
displayName: 'Test Flow',
166+
created: '2023-01-01T00:00:00.000Z',
167+
updated: '2023-01-01T00:00:00.000Z',
168+
updatedBy: 'user_id',
169+
trigger,
170+
valid: true,
171+
state: FlowVersionState.DRAFT,
172+
testRunActionLimits: {
173+
isEnabled: false,
174+
limits: [],
175+
},
176+
};
177+
};
178+
179+
it('should return step and correct index for trigger', () => {
180+
const flowVersion = createSimpleFlowVersion();
181+
const result = flowHelper.getStepWithIndex(flowVersion, 'trigger');
182+
183+
expect(result.step).toBeDefined();
184+
expect(result.step?.name).toBe('trigger');
185+
expect(result.stepIndex).toBe(1);
186+
});
187+
188+
it('should return step and correct index for first action', () => {
189+
const flowVersion = createSimpleFlowVersion();
190+
const result = flowHelper.getStepWithIndex(flowVersion, 'action_1');
191+
192+
expect(result.step).toBeDefined();
193+
expect(result.step?.name).toBe('action_1');
194+
expect(result.stepIndex).toBe(2);
195+
});
196+
197+
it('should return step and correct index for second action', () => {
198+
const flowVersion = createSimpleFlowVersion();
199+
const result = flowHelper.getStepWithIndex(flowVersion, 'action_2');
200+
201+
expect(result.step).toBeDefined();
202+
expect(result.step?.name).toBe('action_2');
203+
expect(result.stepIndex).toBe(3);
204+
});
205+
206+
it('should return undefined for both step and stepIndex when step does not exist', () => {
207+
const flowVersion = createSimpleFlowVersion();
208+
const result = flowHelper.getStepWithIndex(
209+
flowVersion,
210+
'non_existent_step',
211+
);
212+
213+
expect(result.step).toBeUndefined();
214+
expect(result.stepIndex).toBeUndefined();
215+
});
216+
217+
it('should return correct index for step in a branch action', () => {
218+
const flowVersion: FlowVersion = {
219+
id: 'flow_version_id',
220+
flowId: 'flow_id',
221+
displayName: 'Test Flow',
222+
created: '2023-01-01T00:00:00.000Z',
223+
updated: '2023-01-01T00:00:00.000Z',
224+
updatedBy: 'user_id',
225+
trigger: {
226+
id: 'trigger_id',
227+
name: 'trigger',
228+
type: TriggerType.EMPTY,
229+
valid: true,
230+
settings: {},
231+
displayName: 'Trigger',
232+
nextAction: {
233+
id: 'branch_action',
234+
name: 'branch_action',
235+
type: ActionType.BRANCH,
236+
valid: true,
237+
settings: {
238+
conditions: [[]],
239+
},
240+
displayName: 'Branch',
241+
onSuccessAction: {
242+
id: 'success_action',
243+
name: 'success_action',
244+
type: ActionType.CODE,
245+
valid: true,
246+
settings: {
247+
sourceCode: {
248+
code: 'test',
249+
packageJson: '{}',
250+
},
251+
input: {},
252+
},
253+
displayName: 'Success Action',
254+
nextAction: undefined,
255+
},
256+
onFailureAction: {
257+
id: 'failure_action',
258+
name: 'failure_action',
259+
type: ActionType.CODE,
260+
valid: true,
261+
settings: {
262+
sourceCode: {
263+
code: 'test',
264+
packageJson: '{}',
265+
},
266+
input: {},
267+
},
268+
displayName: 'Failure Action',
269+
nextAction: undefined,
270+
},
271+
nextAction: undefined,
272+
},
273+
},
274+
valid: true,
275+
state: FlowVersionState.DRAFT,
276+
testRunActionLimits: {
277+
isEnabled: false,
278+
limits: [],
279+
},
280+
};
281+
282+
const branchResult = flowHelper.getStepWithIndex(
283+
flowVersion,
284+
'branch_action',
285+
);
286+
expect(branchResult.step?.name).toBe('branch_action');
287+
expect(branchResult.stepIndex).toBe(2);
288+
289+
const successResult = flowHelper.getStepWithIndex(
290+
flowVersion,
291+
'success_action',
292+
);
293+
expect(successResult.step?.name).toBe('success_action');
294+
expect(successResult.stepIndex).toBeGreaterThan(2);
295+
296+
const failureResult = flowHelper.getStepWithIndex(
297+
flowVersion,
298+
'failure_action',
299+
);
300+
expect(failureResult.step?.name).toBe('failure_action');
301+
expect(failureResult.stepIndex).toBeGreaterThan(2);
302+
});
303+
304+
it('should return correct index for step in a loop action', () => {
305+
const flowVersion: FlowVersion = {
306+
id: 'flow_version_id',
307+
flowId: 'flow_id',
308+
displayName: 'Test Flow',
309+
created: '2023-01-01T00:00:00.000Z',
310+
updated: '2023-01-01T00:00:00.000Z',
311+
updatedBy: 'user_id',
312+
trigger: {
313+
id: 'trigger_id',
314+
name: 'trigger',
315+
type: TriggerType.EMPTY,
316+
valid: true,
317+
settings: {},
318+
displayName: 'Trigger',
319+
nextAction: {
320+
id: 'loop_action',
321+
name: 'loop_action',
322+
type: ActionType.LOOP_ON_ITEMS,
323+
valid: true,
324+
settings: {
325+
items: '',
326+
},
327+
displayName: 'Loop',
328+
firstLoopAction: {
329+
id: 'loop_child',
330+
name: 'loop_child',
331+
type: ActionType.CODE,
332+
valid: true,
333+
settings: {
334+
sourceCode: {
335+
code: 'test',
336+
packageJson: '{}',
337+
},
338+
input: {},
339+
},
340+
displayName: 'Loop Child',
341+
nextAction: undefined,
342+
},
343+
nextAction: undefined,
344+
},
345+
},
346+
valid: true,
347+
state: FlowVersionState.DRAFT,
348+
testRunActionLimits: {
349+
isEnabled: false,
350+
limits: [],
351+
},
352+
};
353+
354+
const loopResult = flowHelper.getStepWithIndex(
355+
flowVersion,
356+
'loop_action',
357+
);
358+
expect(loopResult.step?.name).toBe('loop_action');
359+
expect(loopResult.stepIndex).toBe(2);
360+
361+
const childResult = flowHelper.getStepWithIndex(
362+
flowVersion,
363+
'loop_child',
364+
);
365+
expect(childResult.step?.name).toBe('loop_child');
366+
expect(childResult.stepIndex).toBeGreaterThan(2);
367+
});
368+
});
119369
});

0 commit comments

Comments
 (0)