mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
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:
committed by
GitHub
parent
2770645eb4
commit
0ce3875f09
@@ -388,6 +388,144 @@ describe('useCanvasOperations', () => {
|
|||||||
|
|
||||||
expect(position).toEqual([0, 0]);
|
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', () => {
|
describe('updateNodesPosition', () => {
|
||||||
|
|||||||
@@ -1029,7 +1029,7 @@ export function useCanvasOperations() {
|
|||||||
|
|
||||||
const nodeSize =
|
const nodeSize =
|
||||||
connectionType === NodeConnectionTypes.Main ? DEFAULT_NODE_SIZE : CONFIGURATION_NODE_SIZE;
|
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;
|
let position: XYPosition | undefined = node.position;
|
||||||
if (position) {
|
if (position) {
|
||||||
@@ -1130,7 +1130,17 @@ export function useCanvasOperations() {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
|
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 (
|
if (
|
||||||
outputTypes.length > 0 &&
|
outputTypes.length > 0 &&
|
||||||
@@ -1152,7 +1162,8 @@ export function useCanvasOperations() {
|
|||||||
lastInteractedWithNode.position[0] +
|
lastInteractedWithNode.position[0] +
|
||||||
(CONFIGURABLE_NODE_SIZE[0] / lastInteractedWithNodeWidthDivisions) *
|
(CONFIGURABLE_NODE_SIZE[0] / lastInteractedWithNodeWidthDivisions) *
|
||||||
(scopedConnectionIndex + 1) -
|
(scopedConnectionIndex + 1) -
|
||||||
nodeSize[0] / 2,
|
nodeSize[0] / 2 -
|
||||||
|
customOffset,
|
||||||
lastInteractedWithNode.position[1] + PUSH_NODES_OFFSET,
|
lastInteractedWithNode.position[1] + PUSH_NODES_OFFSET,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user