fix(editor): Update default positions for AI sub-nodes (no-changelog) (#16870)

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Milorad FIlipović
2025-07-02 09:34:14 +02:00
committed by GitHub
parent 2770645eb4
commit 0ce3875f09
2 changed files with 152 additions and 3 deletions

View File

@@ -388,6 +388,144 @@ describe('useCanvasOperations', () => {
expect(position).toEqual([0, 0]);
});
it('should apply custom Y offset for AI Language Model connection type', () => {
const uiStore = mockedStore(useUIStore);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const node = createTestNode({ id: '0' });
const nodeTypeDescription = mockNodeTypeDescription();
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
uiStore.lastInteractedWithNode = createTestNode({
position: [100, 100],
type: 'test',
typeVersion: 1,
});
uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiLanguageModel}/0`;
nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription);
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
workflowObject.getNode = vi.fn().mockReturnValue(node);
vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([
{ type: NodeConnectionTypes.AiLanguageModel },
]);
vi.spyOn(NodeHelpers, 'getConnectionTypes')
.mockReturnValueOnce([])
.mockReturnValueOnce([])
.mockReturnValueOnce([NodeConnectionTypes.AiLanguageModel]);
const { resolveNodePosition } = useCanvasOperations();
const position = resolveNodePosition({ ...node, position: undefined }, nodeTypeDescription);
// Configuration node size is [200, 80], so customOffset = 200 * 2 = 400
// Expected position: [100 + (200/1) * 1 - 200/2 - 400, 100 + 220] = [-200, 320]
expect(position[0]).toBeLessThan(100); // Node should be moved left due to custom offset
expect(position[1]).toEqual(320); // Standard Y position for configuration nodes
});
it('should apply custom Y offset for AI Memory connection type', () => {
const uiStore = mockedStore(useUIStore);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const node = createTestNode({ id: '0' });
const nodeTypeDescription = mockNodeTypeDescription();
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
uiStore.lastInteractedWithNode = createTestNode({
position: [100, 100],
type: 'test',
typeVersion: 1,
});
uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiMemory}/0`;
nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription);
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
workflowObject.getNode = vi.fn().mockReturnValue(node);
vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([
{ type: NodeConnectionTypes.AiMemory },
]);
vi.spyOn(NodeHelpers, 'getConnectionTypes')
.mockReturnValueOnce([])
.mockReturnValueOnce([])
.mockReturnValueOnce([NodeConnectionTypes.AiMemory]);
const { resolveNodePosition } = useCanvasOperations();
const position = resolveNodePosition({ ...node, position: undefined }, nodeTypeDescription);
expect(position[0]).toBeLessThan(100); // Node should be moved left due to custom offset
expect(position[1]).toEqual(320); // Standard Y position for configuration nodes
});
it('should not apply custom offset for regular connection types', () => {
const uiStore = mockedStore(useUIStore);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const node = createTestNode({ id: '0' });
const nodeTypeDescription = mockNodeTypeDescription();
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
uiStore.lastInteractedWithNode = createTestNode({
position: [100, 100],
type: 'test',
typeVersion: 1,
});
uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`;
nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription);
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
workflowObject.getNode = vi.fn().mockReturnValue(node);
vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([
{ type: NodeConnectionTypes.AiTool },
]);
vi.spyOn(NodeHelpers, 'getConnectionTypes')
.mockReturnValueOnce([])
.mockReturnValueOnce([])
.mockReturnValueOnce([NodeConnectionTypes.AiTool]);
const { resolveNodePosition } = useCanvasOperations();
const position = resolveNodePosition({ ...node, position: undefined }, nodeTypeDescription);
// No custom offset applied
expect(position).toEqual([60, 320]);
});
it('should handle missing connection type gracefully', () => {
const uiStore = mockedStore(useUIStore);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const node = createTestNode({ id: '0' });
const nodeTypeDescription = mockNodeTypeDescription();
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
uiStore.lastInteractedWithNode = createTestNode({
position: [100, 100],
type: 'test',
typeVersion: 1,
});
// No lastInteractedWithNodeHandle set
nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription);
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
workflowObject.getNode = vi.fn().mockReturnValue(node);
vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([
{ type: NodeConnectionTypes.AiTool },
]);
vi.spyOn(NodeHelpers, 'getConnectionTypes')
.mockReturnValueOnce([])
.mockReturnValueOnce([])
.mockReturnValueOnce([NodeConnectionTypes.AiTool]);
const { resolveNodePosition } = useCanvasOperations();
const position = resolveNodePosition({ ...node, position: undefined }, nodeTypeDescription);
// No custom offset applied
expect(position).toEqual([60, 320]);
});
});
describe('updateNodesPosition', () => {

View File

@@ -1029,7 +1029,7 @@ export function useCanvasOperations() {
const nodeSize =
connectionType === NodeConnectionTypes.Main ? DEFAULT_NODE_SIZE : CONFIGURATION_NODE_SIZE;
let pushOffsets: XYPosition = [nodeSize[0] / 2, nodeSize[1] / 2];
const pushOffsets: XYPosition = [nodeSize[0] / 2, nodeSize[1] / 2];
let position: XYPosition | undefined = node.position;
if (position) {
@@ -1130,7 +1130,17 @@ export function useCanvasOperations() {
} catch (e) {}
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
pushOffsets = [100, 0];
/**
* Custom Y offsets for specific connection types when adding them using the plus button:
* - AI Language Model: Moved left by 2 node widths
* - AI Memory: Moved left by 1 node width
*/
const CUSTOM_Y_OFFSETS: Record<string, number> = {
[NodeConnectionTypes.AiLanguageModel]: nodeSize[0] * 2,
[NodeConnectionTypes.AiMemory]: nodeSize[0],
};
const customOffset: number = CUSTOM_Y_OFFSETS[connectionType as string] ?? 0;
if (
outputTypes.length > 0 &&
@@ -1152,7 +1162,8 @@ export function useCanvasOperations() {
lastInteractedWithNode.position[0] +
(CONFIGURABLE_NODE_SIZE[0] / lastInteractedWithNodeWidthDivisions) *
(scopedConnectionIndex + 1) -
nodeSize[0] / 2,
nodeSize[0] / 2 -
customOffset,
lastInteractedWithNode.position[1] + PUSH_NODES_OFFSET,
];
} else {