From 86c9ade93e61d1cbb60fe49c8e06be77f1535ed8 Mon Sep 17 00:00:00 2001
From: huangmin <2927933426@qq.com>
Date: Mon, 8 Jun 2026 12:37:19 +0800
Subject: [PATCH] =?UTF-8?q?fix(GenerationComposer):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E5=88=86=E7=BB=84=E8=8A=82=E7=82=B9=E5=86=85=E9=80=89=E4=B8=AD?=
=?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E7=9A=84=E5=AE=9A=E4=BD=8D=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增getAbsoluteNodePosition函数计算节点基于画布的绝对坐标,替换原有相对父节点的位置计算逻辑,确保分组内节点的编辑器能正确显示在对应位置
---
.../__tests__/GenerationComposer.test.tsx | 30 ++++++++++++++++
.../composer/GenerationComposer.tsx | 34 +++++++++++++++++--
2 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/src/components/__tests__/GenerationComposer.test.tsx b/src/components/__tests__/GenerationComposer.test.tsx
index b28a1bf7..6941e0ac 100644
--- a/src/components/__tests__/GenerationComposer.test.tsx
+++ b/src/components/__tests__/GenerationComposer.test.tsx
@@ -427,6 +427,36 @@ describe("GenerationComposer", () => {
expect(screen.getByPlaceholderText(/Describe what you want to generate/)).toHaveClass("h-[13.5rem]");
});
+ it("positions the selected node editor below grouped nodes using absolute canvas coordinates", () => {
+ useWorkflowStore.setState({
+ nodes: [
+ {
+ id: "group-node-group-1",
+ type: "group",
+ position: { x: 400, y: 200 },
+ data: { groupId: "group-1" },
+ style: { width: 500, height: 360 },
+ } as WorkflowNode,
+ {
+ ...imageNode("img-1", true),
+ parentId: "group-node-group-1",
+ extent: "parent",
+ expandParent: true,
+ groupId: "group-1",
+ position: { x: 100, y: 40 },
+ measured: { width: 260, height: 180 },
+ } as WorkflowNode,
+ ],
+ });
+
+ render();
+
+ expect(screen.getByTestId("node-inline-composer")).toHaveStyle({
+ left: "108px",
+ top: "436px",
+ });
+ });
+
it("keeps the selected node editor below the node when below would leave the viewport", () => {
const originalInnerHeight = window.innerHeight;
const originalInnerWidth = window.innerWidth;
diff --git a/src/components/composer/GenerationComposer.tsx b/src/components/composer/GenerationComposer.tsx
index 4194674b..03d92957 100644
--- a/src/components/composer/GenerationComposer.tsx
+++ b/src/components/composer/GenerationComposer.tsx
@@ -892,6 +892,30 @@ function getAdapter(nodeType: NodeType | null): ComposerAdapter [item.id, item]));
+ const visited = new Set();
+
+ const resolve = (current: WorkflowNode): { x: number; y: number } => {
+ if (!current.parentId || visited.has(current.id)) return current.position;
+ visited.add(current.id);
+
+ const parent = nodeById.get(current.parentId);
+ if (!parent) return current.position;
+
+ const parentPosition = resolve(parent);
+ return {
+ x: parentPosition.x + current.position.x,
+ y: parentPosition.y + current.position.y,
+ };
+ };
+
+ return resolve(node);
+}
+
function deriveComposerContext(nodes: WorkflowNode[], edges: WorkflowEdge[]): ComposerContext {
const selectedNodes = nodes.filter((node) => node.selected);
if (selectedNodes.length === 0) {
@@ -1702,6 +1726,10 @@ export function GenerationComposer() {
() => context.node ? getComposerNodeDimensions(context.node) : null,
[context.node]
);
+ const selectedNodePosition = useMemo(
+ () => context.node ? getAbsoluteNodePosition(context.node, nodes) : null,
+ [context.node, nodes]
+ );
const safeZoom = zoom > 0 ? zoom : 1;
const viewportWidth = typeof window === "undefined" ? Number.POSITIVE_INFINITY : window.innerWidth;
const visibleLeft = -viewportX / safeZoom;
@@ -1709,15 +1737,15 @@ export function GenerationComposer() {
const composerWidth = isComposerCollapsed ? COMPOSER_COLLAPSED_WIDTH : COMPOSER_EXPANDED_WIDTH;
const composerFlowWidth = composerWidth / safeZoom;
const viewportMargin = COMPOSER_VIEWPORT_MARGIN / safeZoom;
- const inlineComposerPosition = context.node && selectedNodeDimensions
+ const inlineComposerPosition = context.node && selectedNodeDimensions && selectedNodePosition
? (() => {
- const centeredLeft = context.node.position.x + selectedNodeDimensions.width / 2 - composerFlowWidth / 2;
+ const centeredLeft = selectedNodePosition.x + selectedNodeDimensions.width / 2 - composerFlowWidth / 2;
const minLeft = visibleLeft + viewportMargin;
const maxLeft = visibleRight - composerFlowWidth - viewportMargin;
const left = maxLeft >= minLeft
? Math.min(Math.max(centeredLeft, minLeft), maxLeft)
: minLeft;
- const belowTop = context.node.position.y + selectedNodeDimensions.height + viewportMargin;
+ const belowTop = selectedNodePosition.y + selectedNodeDimensions.height + viewportMargin;
return {
left,