Browse Source

Stop duplicate image settings from competing with composer

The bottom composer is the canonical image-generation control surface now. The legacy right-side control panel was still rendering schema defaults for selected image nodes, which made aspect and size controls disagree on the same operation.

Constraint: Image generation controls must stay consistent with the Popi composer UX

Rejected: Synchronize both panels | duplicating controls preserves the confusing two-source UI

Confidence: high

Scope-risk: narrow

Directive: Do not re-enable the right-side image panel unless it reads from the same composer-owned state

Tested: npm run test:run -- src/components/__tests__/ControlPanel.test.tsx

Tested: npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx src/components/__tests__/ControlPanel.test.tsx

Tested: PROVIDER_MODE=popi NEXT_PUBLIC_PROVIDER_MODE=popi npm run build

Tested: Playwright localhost check for composer visible and legacy image settings absent
feature/add_agent_md
jiajia 2 months ago
parent
commit
ad4779884f
  1. 101
      src/components/__tests__/ControlPanel.test.tsx
  2. 10
      src/components/nodes/ControlPanel.tsx

101
src/components/__tests__/ControlPanel.test.tsx

@ -0,0 +1,101 @@
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ControlPanel } from "@/components/nodes/ControlPanel";
import { useWorkflowStore } from "@/store/workflowStore";
import type { WorkflowNode } from "@/types";
vi.mock("@/hooks/useInlineParameters", () => ({
useInlineParameters: () => ({ inlineParametersEnabled: false }),
}));
vi.mock("@/components/nodes/ModelParameters", () => ({
ModelParameters: () => <div data-testid="model-parameters" />,
}));
vi.mock("@/components/modals/ModelSearchDialog", () => ({
ModelSearchDialog: () => null,
}));
function imageNode(): WorkflowNode {
return {
id: "image-1",
type: "nanoBanana",
selected: true,
position: { x: 0, y: 0 },
data: {
inputImages: [],
inputPrompt: "coffee spilled",
outputImage: null,
aspectRatio: "16:9",
resolution: "1K",
model: "nano-banana-pro",
selectedModel: {
provider: "newapiwg",
modelId: "apiyi_nano_banana_2",
displayName: "apiyi_nano_banana_2",
capabilities: ["text-to-image", "image-to-image"],
},
parameters: { quality: "auto", size: "1024x1024" },
useGoogleSearch: false,
useImageSearch: false,
status: "idle",
error: null,
imageHistory: [],
selectedHistoryIndex: 0,
},
} as WorkflowNode;
}
function videoNode(): WorkflowNode {
return {
id: "video-1",
type: "generateVideo",
selected: true,
position: { x: 0, y: 0 },
data: {
prompt: "camera pan",
inputImage: null,
outputVideo: null,
selectedModel: {
provider: "newapiwg",
modelId: "video-model",
displayName: "Video Model",
capabilities: ["text-to-video"],
},
parameters: {},
status: "idle",
error: null,
progress: 0,
},
} as WorkflowNode;
}
describe("ControlPanel", () => {
beforeEach(() => {
vi.clearAllMocks();
useWorkflowStore.setState({
nodes: [],
edges: [],
isRunning: false,
});
});
it("does not render duplicate settings for image generation nodes controlled by the composer", () => {
useWorkflowStore.setState({ nodes: [imageNode()] });
const { container } = render(<ControlPanel />);
expect(container).toBeEmptyDOMElement();
expect(screen.queryByText("Generate Image Settings")).not.toBeInTheDocument();
expect(screen.queryByText("1024x1024")).not.toBeInTheDocument();
});
it("keeps the right-side panel for generation nodes not yet owned by the composer", () => {
useWorkflowStore.setState({ nodes: [videoNode()] });
render(<ControlPanel />);
expect(screen.getByText("Generate Video Settings")).toBeInTheDocument();
expect(screen.getByTestId("model-parameters")).toBeInTheDocument();
});
});

10
src/components/nodes/ControlPanel.tsx

@ -39,6 +39,10 @@ const GENERATION_NODE_TYPES: NodeType[] = [
"llmGenerate", "llmGenerate",
]; ];
const COMPOSER_CONTROLLED_NODE_TYPES: NodeType[] = [
"nanoBanana",
];
// Base 10 aspect ratios (all Gemini image models) // Base 10 aspect ratios (all Gemini image models)
const BASE_ASPECT_RATIOS: AspectRatio[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; const BASE_ASPECT_RATIOS: AspectRatio[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
@ -101,6 +105,12 @@ export function ControlPanel() {
return null; return null;
} }
// Image generation settings live in the bottom composer. Keeping the old
// right-side panel visible creates duplicate controls with separate defaults.
if (COMPOSER_CONTROLLED_NODE_TYPES.includes(selectedNode.type as NodeType)) {
return null;
}
// Check if this is a generation node // Check if this is a generation node
const isGenerationNode = selectedNode && const isGenerationNode = selectedNode &&
GENERATION_NODE_TYPES.includes(selectedNode.type as NodeType); GENERATION_NODE_TYPES.includes(selectedNode.type as NodeType);

Loading…
Cancel
Save