first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export * as listSearch from './listSearch';
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
audioModelSearch,
|
||||
imageEditModelSearch,
|
||||
imageGenerationModelSearch,
|
||||
modelSearch,
|
||||
videoGenerationModelSearch,
|
||||
} from './listSearch';
|
||||
import * as transport from '../transport';
|
||||
|
||||
const mockResponse = {
|
||||
models: [
|
||||
{
|
||||
name: 'models/gemini-pro-vision',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.5-flash',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.0-flash-exp-image-generation',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.5-pro-preview-tts',
|
||||
},
|
||||
{
|
||||
name: 'models/gemma-3-1b-it',
|
||||
},
|
||||
{
|
||||
name: 'models/embedding-001',
|
||||
},
|
||||
{
|
||||
name: 'models/imagen-3.0-generate-002',
|
||||
},
|
||||
{
|
||||
name: 'models/veo-2.0-generate-001',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.5-flash-preview-native-audio-dialog',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.5-flash-image',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-3-pro-image',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('GoogleGemini -> listSearch', () => {
|
||||
const mockExecuteFunctions = mock<ILoadOptionsFunctions>();
|
||||
const apiRequestMock = jest.spyOn(transport, 'apiRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('modelSearch', () => {
|
||||
it('should return regular models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await modelSearch.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/gemini-2.5-flash',
|
||||
value: 'models/gemini-2.5-flash',
|
||||
},
|
||||
{
|
||||
name: 'models/gemma-3-1b-it',
|
||||
value: 'models/gemma-3-1b-it',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return regular models with filter', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await modelSearch.call(mockExecuteFunctions, 'Gemma');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/gemma-3-1b-it',
|
||||
value: 'models/gemma-3-1b-it',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('audioModelSearch', () => {
|
||||
it('should return audio models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await audioModelSearch.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/gemini-2.5-flash',
|
||||
value: 'models/gemini-2.5-flash',
|
||||
},
|
||||
{
|
||||
name: 'models/gemma-3-1b-it',
|
||||
value: 'models/gemma-3-1b-it',
|
||||
},
|
||||
{
|
||||
name: 'models/gemini-2.5-flash-preview-native-audio-dialog',
|
||||
value: 'models/gemini-2.5-flash-preview-native-audio-dialog',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('imageModelSearch', () => {
|
||||
it('should return image models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await imageGenerationModelSearch.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/gemini-2.0-flash-exp-image-generation',
|
||||
value: 'models/gemini-2.0-flash-exp-image-generation',
|
||||
},
|
||||
{
|
||||
name: 'models/imagen-3.0-generate-002',
|
||||
value: 'models/imagen-3.0-generate-002',
|
||||
},
|
||||
{
|
||||
// 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',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter out image models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await imageGenerationModelSearch.call(mockExecuteFunctions, 'Exp');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/gemini-2.0-flash-exp-image-generation',
|
||||
value: 'models/gemini-2.0-flash-exp-image-generation',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter out image edit models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await imageEditModelSearch.call(mockExecuteFunctions, 'banana pro');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
// 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('videoModelSearch', () => {
|
||||
it('should return video models', async () => {
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await videoGenerationModelSearch.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
name: 'models/veo-2.0-generate-001',
|
||||
value: 'models/veo-2.0-generate-001',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import type { ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../transport';
|
||||
|
||||
async function baseModelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
modelFilter: (model: string) => boolean,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const response = (await apiRequest.call(this, 'GET', '/v1beta/models', {
|
||||
qs: {
|
||||
pageSize: 1000,
|
||||
},
|
||||
})) as {
|
||||
models: Array<{ name: string }>;
|
||||
};
|
||||
|
||||
let models = response.models.filter((model) => modelFilter(model.name));
|
||||
if (filter) {
|
||||
models = models.filter((model) => model.name.toLowerCase().includes(filter.toLowerCase()));
|
||||
}
|
||||
|
||||
return {
|
||||
results: models.map((model) => ({ name: model.name, value: model.name })),
|
||||
};
|
||||
}
|
||||
|
||||
export async function modelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
return await baseModelSearch.call(
|
||||
this,
|
||||
(model) =>
|
||||
!model.includes('embedding') &&
|
||||
!model.includes('aqa') &&
|
||||
!model.includes('image') &&
|
||||
!model.includes('vision') &&
|
||||
!model.includes('veo') &&
|
||||
!model.includes('audio') &&
|
||||
!model.includes('tts'),
|
||||
filter,
|
||||
);
|
||||
}
|
||||
|
||||
export async function audioModelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
return await baseModelSearch.call(
|
||||
this,
|
||||
(model) =>
|
||||
!model.includes('embedding') &&
|
||||
!model.includes('aqa') &&
|
||||
!model.includes('image') &&
|
||||
!model.includes('vision') &&
|
||||
!model.includes('veo') &&
|
||||
!model.includes('tts'), // we don't have a tts operation
|
||||
filter,
|
||||
);
|
||||
}
|
||||
|
||||
export async function imageGenerationModelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const rawResult = await baseModelSearch.call(this, (model) => model.includes('image'));
|
||||
let results = rawResult.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;
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
const filterLowerCase = filter.toLowerCase();
|
||||
results = results.filter((r) => r.name.toLowerCase().includes(filterLowerCase));
|
||||
}
|
||||
|
||||
return {
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
export async function imageEditModelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const result = await imageGenerationModelSearch.call(this, filter);
|
||||
return {
|
||||
results: result.results.filter((r) => r.name.toLowerCase().includes('nano banana')),
|
||||
};
|
||||
}
|
||||
|
||||
export async function videoGenerationModelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
return await baseModelSearch.call(this, (model) => model.includes('veo'), filter);
|
||||
}
|
||||
Reference in New Issue
Block a user