Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { updateDisplayOptions } from 'n8n-workflow';
import type { GenerateContentResponse } from '../../helpers/interfaces';
import { uploadFile } from '../../helpers/utils';
import { apiRequest } from '../../transport';
import { modelRLC } from '../descriptions';

interface ImagesParameter {
values?: Array<{ binaryPropertyName?: string }>;
Expand Down Expand Up @@ -74,6 +75,7 @@ function isGenerateContentResponse(response: unknown): response is GenerateConte
}

const properties: INodeProperties[] = [
modelRLC('imageEditModelSearch'),
{
displayName: 'Prompt',
name: 'prompt',
Expand Down Expand Up @@ -142,6 +144,7 @@ export const description = updateDisplayOptions(displayOptions, properties);

export async function execute(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]> {
const prompt = this.getNodeParameter('prompt', i, '');
const model = this.getNodeParameter('modelId', i, '', { extractValue: true }) as string;
const binaryPropertyOutput = this.getNodeParameter('options.binaryPropertyOutput', i, 'edited');
const outputKey = typeof binaryPropertyOutput === 'string' ? binaryPropertyOutput : 'data';

Expand All @@ -168,7 +171,6 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
fileParts.push({ fileData: { fileUri: uploaded.fileUri, mimeType: uploaded.mimeType } });
}

const model = 'models/gemini-2.5-flash-image-preview';
const generationConfig = {
responseModalities: ['IMAGE'],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
});

return await Promise.all(promises);
} else if (model.includes('imagen') || model.includes('flash-image')) {
} else if (model.includes('imagen')) {
// Imagen models use a different endpoint and request/response structure
const sampleCount = this.getNodeParameter('options.sampleCount', i, 1) as number;
const body = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ILoadOptionsFunctions } from 'n8n-workflow';

import {
audioModelSearch,
imageEditModelSearch,
imageGenerationModelSearch,
modelSearch,
videoGenerationModelSearch,
Expand Down Expand Up @@ -41,6 +42,9 @@ const mockResponse = {
{
name: 'models/gemini-2.5-flash-image',
},
{
name: 'models/gemini-3-pro-image',
},
],
};

Expand Down Expand Up @@ -134,6 +138,34 @@ describe('GoogleGemini -> listSearch', () => {
name: 'models/gemini-2.5-flash-image (Nano Banana)',
value: 'models/gemini-2.5-flash-image',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'models/gemini-3-pro-image (Nano Banana Pro)',
value: 'models/gemini-3-pro-image',
},
],
});
});
});

describe('imageEditModelSearch', () => {
it('should return image edit models', async () => {
apiRequestMock.mockResolvedValue(mockResponse);

const result = await imageEditModelSearch.call(mockExecuteFunctions);

expect(result).toEqual({
results: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'models/gemini-2.5-flash-image (Nano Banana)',
value: 'models/gemini-2.5-flash-image',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'models/gemini-3-pro-image (Nano Banana Pro)',
value: 'models/gemini-3-pro-image',
},
],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,29 @@ export async function imageGenerationModelSearch(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const results = await baseModelSearch.call(
this,
(model) =>
model.includes('imagen') ||
model.includes('image-generation') ||
model.includes('flash-image'),
filter,
);
const results = await baseModelSearch.call(this, (model) => model.includes('image'), filter);
return {
results: results.results.map((r) => {
if (r.name.includes('gemini-2.5-flash-image')) {
return { name: `${r.name} (Nano Banana)`, value: r.value };
}

if (r.name.includes('gemini-3-pro-image')) {
return { name: `${r.name} (Nano Banana Pro)`, value: r.value };
}

return r;
}),
};
}

export async function imageEditModelSearch(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const result = await imageGenerationModelSearch.call(this, filter);
return {
results: results.results.map((r) =>
r.name.includes('gemini-2.5-flash-image')
? { name: `${r.name} (Nano Banana)`, value: r.value }
: r,
),
results: result.results.filter((r) => r.name.includes('Nano Banana')),
};
}

Expand Down
Loading