Bulk AI Flow
This plugin allows filling fields in multiple selected records based on data from other fields using LLM. This also supports vision tasks so you can ask it to e.g. detect dominant color on image or describe what is on the image. Plugin supports classification to enum options automatically.
Installation
To install the plugin:
npm install @adminforth/bulk-ai-flow --save
You'll also need an image vision adapter:
npm install @adminforth/image-vision-adapter-openai --save
Vision mode
This mode covers next generations:
- Image(one or many fields) -> to -> Text/Number/Enum/Boolean(one or many fields)
- Image(one or many fields) + Text/Number/Enum/Boolean(one or many fields) -> to -> Text/Number/Enum/Boolean(one or many fields)
Lets try both. Add a column for storing the URL or path to the image in the database, add this statement to the ./schema.prisma
:
model apartments {
id String @id
created_at DateTime?
title String
square_meter Float?
price Decimal
number_of_rooms Int?
description String?
country String?
listed Boolean
realtor_id String?
apartment_image String?
}
Migrate prisma schema:
npm run makemigration -- --name add-apartment-image-url ; npm run migrate:local
We will also attach upload plugin to this field.
Add credentials in your .env
file:
...
OPENAI_API_KEY=your_secret_openai_key
...
Add column to aparts
resource configuration:
import BulkAiFlowPlugin from '@adminforth/bulk-ai-flow';
import AdminForthImageVisionAdapterOpenAi from '@adminforth/image-vision-adapter-openai';
import UploadPlugin from '@adminforth/upload';
import { randomUUID } from 'crypto';
import AdminForthAdapterS3Storage from '@adminforth/storage-adapter-amazon-s3'
export const admin = new AdminForth({
...
resourceId: 'aparts',
columns: [
...
{
name: 'apartment_image',
label: 'Image',
showIn: { list: false, create: true, edit: true},
}
...
],
plugins: [
...
new UploadPlugin({
storageAdapter: new AdminForthAdapterS3Storage({
bucket: process.env.AWS_BUCKET_NAME,
region: process.env.AWS_REGION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
s3ACL: 'public-read',
}),
pathColumnName: 'apartment_image',
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'webp'],
filePath: ({originalFilename, originalExtension, contentType}) =>
`aparts/${new Date().getFullYear()}/${uuid()}-${originalFilename}.${originalExtension}`,
}),
new BulkAiFlowPlugin({
actionName: 'Analyze',
attachFiles: async ({ record }: { record: any }) => {
if (!record.apartment_image) {
return [];
}
return [`https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${record.apartment_image}`];
},
visionAdapter: new AdminForthImageVisionAdapterOpenAi(
{
openAiApiKey: process.env.OPENAI_API_KEY as string,
model: 'gpt-4.1-mini',
}
),
fillFieldsFromImages: {
'description': 'describe what is in the image, also take into account that price is {{price}}',
'country': 'In which country it can be located?',
'number_of_rooms': 'How many rooms are in the apartment? Just try to guess what is a typical one. If you do not know, just guess',
'square_meter': 'Try to guess what is the typical square of the apartment in square meters? If you do not know, just guess',
'listed': 'Is the apartment should be listed for sale? If you do not know, just guess, return boolean value',
},
}),
],
...
});
⚠️ Make sure your attachFiles function returns a valid array of image URLs or an empty array. Returning anything else may cause an error.
Usage
- Select fields you want to fill
- Click on the three dots menu
- Click analyze
- Wait for finish analyze
- Check and edit result
- Save changhes
Text-to-Text Processing
This is the most basic plugin usage. You can connect any text completion adapter to fill one or several string/number/boolean fields from other fields.
Example: Translate Names to English
Normalize user names by translating them from any language to English for internal processing.
import CompletionAdapterOpenAIChatGPT from '@adminforth/completion-adapter-open-ai-chat-gpt/index.js';
// Add to your resource plugins array
new BulkAiFlowPlugin({
actionName: 'Translate surnames',
textCompleteAdapter: new CompletionAdapterOpenAIChatGPT({
openAiApiKey: process.env.OPENAI_API_KEY as string,
model: 'gpt-4o',
expert: {
temperature: 0.7
}
}),
fillPlainFields: {
'full_name_en': 'Translate this name to English: {{users_full_name}}',
},
}),
Image-to-Text Analysis (Vision)
Analyze images and extract information to fill text, number, enum, or boolean fields.
Example: Age Detection from Photos
import AdminForthImageVisionAdapterOpenAi from '@adminforth/image-vision-adapter-openai/index.js';
// Add to your resource plugins array
new BulkAiFlowPlugin({
actionName: 'Guess age',
visionAdapter: new AdminForthImageVisionAdapterOpenAi({
openAiApiKey: process.env.OPENAI_API_KEY as string,
model: 'gpt-4.1-mini',
}),
fillFieldsFromImages: {
'age': 'Analyze the image and estimate the age of the person. Return only a number.',
},
attachFiles: async ({ record }) => {
if (!record.image_url) {
return [];
}
return [`https://users-images.s3.eu-north-1.amazonaws.com/${record.image_url}`];
},
}),
Text-to-Image generation or image editing
Generate new images based on existing data and/or images using AI image generation adapters.
Example: Creating Cartoon Avatars
import ImageGenerationAdapterOpenAI from '@adminforth/image-generation-adapter-openai/index.js';
// Add to your resource plugins array
new BulkAiFlowPlugin({
actionName: 'Generate cartoon avatars',
imageGenerationAdapter: new ImageGenerationAdapterOpenAI({
openAiApiKey: process.env.OPENAI_API_KEY as string,
model: 'gpt-image-1',
}),
attachFiles: async ({ record }) => {
if (!record.user_photo) {
return [];
}
return [`https://bulk-ai-flow-playground.s3.eu-north-1.amazonaws.com/${record.users_photo}`];
},
generateImages: {
users_avatar: {
prompt: 'Transform this photo into a cartoon-style avatar. Maintain the person\'s features but apply cartoon styling. Do not add text or logos.',
outputSize: '1024x1024',
countToGenerate: 2,
rateLimit: '3/1h'
},
},
bulkGenerationRateLimit: "1/1h"
}),
Rate Limiting and Best Practices
- Use
rateLimit
for individual image generation operations and for the bulk image generation
new BulkAiFlowPlugin({
...
generateImages: {
users_avatar: {
... //image re-generation limits
rateLimit: '1/5m' // one request per 5 minutes
}
},
...
rateLimits: { // bulk generation limits
fillFieldsFromImages: "5/1d", // 5 requests per day
fillPlainFields: "3/1h", // 3 requests per one hour
generateImages: "1/2m", // 2 request per one minute
}
...
})
- Consider using lower resolution (
512x512
) for faster generation and lower costs - Test prompts thoroughly before applying to large datasets