diff --git a/src/components/__tests__/GenerateImageNode.test.tsx b/src/components/__tests__/GenerateImageNode.test.tsx index 40689faa..0c485162 100644 --- a/src/components/__tests__/GenerateImageNode.test.tsx +++ b/src/components/__tests__/GenerateImageNode.test.tsx @@ -362,6 +362,46 @@ describe("GenerateImageNode", () => { expect(screen.getByTitle("Next image")).toBeInTheDocument(); }); + it("should hide carousel controls when generations path is not configured", () => { + mockUseWorkflowStore.mockImplementation((selector) => { + const state = { + updateNodeData: mockUpdateNodeData, + regenerateNode: mockRegenerateNode, + addNode: mockAddNode, + incrementModalCount: mockIncrementModalCount, + decrementModalCount: mockDecrementModalCount, + providerSettings: defaultProviderSettings, + generationsPath: null, + isRunning: false, + currentNodeIds: [], + groups: {}, + nodes: [], + recentModels: [], + trackModelUsage: vi.fn(), + getNodesWithComments: vi.fn(() => []), + markCommentViewed: vi.fn(), + setNavigationTarget: vi.fn(), + }; + return selector(state); + }); + + render( + + + + ); + + expect(screen.queryByTitle("Previous image")).not.toBeInTheDocument(); + expect(screen.queryByTitle("Next image")).not.toBeInTheDocument(); + }); + it("should show current position in carousel", () => { render( diff --git a/src/components/nodes/GenerateImageNode.tsx b/src/components/nodes/GenerateImageNode.tsx index 08c4011e..e72c5c8b 100644 --- a/src/components/nodes/GenerateImageNode.tsx +++ b/src/components/nodes/GenerateImageNode.tsx @@ -335,7 +335,6 @@ export function GenerateImageNode({ id, data, selected }: NodeProps { if (!generationsPath) { - console.error("Generations path not configured"); return null; } @@ -445,7 +444,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps 1; + const hasCarouselImages = Boolean(generationsPath) && (nodeData.imageHistory || []).length > 1; // Count visible Gemini controls to match ModelParameters grid/max-width rules const geminiControlCount = 2 // Model + Aspect Ratio (always) diff --git a/src/store/execution/__tests__/nanoBananaExecutor.test.ts b/src/store/execution/__tests__/nanoBananaExecutor.test.ts index b63d265b..6f72a2fc 100644 --- a/src/store/execution/__tests__/nanoBananaExecutor.test.ts +++ b/src/store/execution/__tests__/nanoBananaExecutor.test.ts @@ -216,7 +216,8 @@ describe("executeNanoBanana", () => { const body = JSON.parse(mockFetch.mock.calls[0][1].body); expect(body.prompt).toContain("right side key light"); expect(body.prompt).toContain("70% brightness"); - expect(body.prompt).toContain("Preserve the original subject identity"); + expect(body.prompt).toContain("same exact person"); + expect(body.prompt).toContain("Only change illumination"); }); it("uses NewApiWG default for legacy image nodes when Gemini is not configured", async () => { diff --git a/src/utils/__tests__/lighting.test.ts b/src/utils/__tests__/lighting.test.ts index 25f6a76b..16e4729a 100644 --- a/src/utils/__tests__/lighting.test.ts +++ b/src/utils/__tests__/lighting.test.ts @@ -38,10 +38,13 @@ describe("lighting utilities", () => { mainLightDirection: "front", }); - expect(prompt).toContain("Image editing task"); + expect(prompt).toContain("STRICT image-to-image relighting task"); expect(prompt).toContain("uploaded image"); expect(prompt).toContain("relight the uploaded image"); - expect(prompt).toContain("Only change the lighting"); - expect(prompt).toContain("Do not add new people or objects"); + expect(prompt).toContain("same exact person"); + expect(prompt).toContain("Preserve the original composition"); + expect(prompt).toContain("Only change illumination"); + expect(prompt).toContain("lighting reference only"); + expect(prompt).toContain("Do not add or remove people or objects"); }); }); diff --git a/src/utils/lighting.ts b/src/utils/lighting.ts index 6d3fc96f..40c5f5d8 100644 --- a/src/utils/lighting.ts +++ b/src/utils/lighting.ts @@ -27,7 +27,7 @@ export const LIGHTING_PRESETS: LightingPreset[] = [ mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, - smartPrompt: "柯达胶片质感", + smartPrompt: "柯达胶片感的曝光和柔和高光,只调整打光", }, }, { @@ -71,7 +71,7 @@ export const LIGHTING_PRESETS: LightingPreset[] = [ lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, rimLight: false, - smartPrompt: "让画面拥有《银翼杀手2045》同款打光,极简背景", + smartPrompt: "《银翼杀手2045》同款青紫高反差打光,只调整光影", }, }, { @@ -113,7 +113,7 @@ export const LIGHTING_PRESETS: LightingPreset[] = [ mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, - smartPrompt: "让画面光影变成“黄金时刻”", + smartPrompt: "黄金时刻暖色光影,只调整光照和阴影", }, }, { @@ -127,7 +127,7 @@ export const LIGHTING_PRESETS: LightingPreset[] = [ mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, - smartPrompt: "让画面质感变成清晨同款冷灰色调", + smartPrompt: "清晨冷灰色温和柔和阴影,只调整光照和色温", }, }, ]; @@ -236,11 +236,13 @@ export function buildOptimizedLightingPrompt( : "User intent: relight the uploaded image according to the lighting plan."; return [ - "Image editing task: use the uploaded image as the source image and generate one finished relit image.", + "STRICT image-to-image relighting task: use the uploaded image as the locked source image.", userIntent, lightingPrompt, - "Preserve the original subject identity, pose, clothing, composition, camera angle, and image style as much as possible.", - "Only change the lighting, shadow direction, highlight intensity, and ambient mood required by the lighting plan.", - "Do not add new people or objects, do not crop the subject, do not change the character design, and do not create a different scene.", + "The output must show the same exact person from the source image: identical face identity, facial features, age, expression, hair, body shape, hands, pose, clothing, accessories, and camera/phone.", + "Preserve the original composition, camera angle, crop, background, object layout, readable text overlays, and image style.", + "Only change illumination: light direction, brightness, color temperature, shadows, highlights, reflections, and ambient lighting mood required by the lighting plan.", + "Treat any preset or style wording as a lighting reference only, not permission to redesign the person, outfit, background, pose, scene, or photographic composition.", + "Do not add or remove people or objects, do not beautify or re-age the person, do not replace the face, do not change hairstyle or clothing, do not crop the subject, and do not create a different scene.", ].join("\n"); }