From 47c2d472658fe8daf6e6e784a9b1cd2c7380d4f5 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Fri, 14 Nov 2025 13:41:06 +0200 Subject: [PATCH 1/6] feat: add support for additional context in template compilation --- index.ts | 30 +++++++++++++++++------------- types.ts | 12 ++++++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/index.ts b/index.ts index ace0a5e..6706e50 100644 --- a/index.ts +++ b/index.ts @@ -25,11 +25,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } // Compile Handlebars templates in outputFields using record fields as context - private compileTemplates>( + private async compileTemplates>( source: T, record: any, valueSelector: (value: T[keyof T]) => string - ): Record { + ): Promise> { + if (this.options.provideAdditionalContextForRecord) { + const additionalFields = await this.options.provideAdditionalContextForRecord({ record, adminUser: null, resource: this.resourceConfig }); + record = { ...record, ...additionalFields }; + } const compiled: Record = {}; for (const [key, value] of Object.entries(source)) { const templateStr = valueSelector(value); @@ -43,16 +47,16 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { return compiled; } - private compileOutputFieldsTemplates(record: any) { - return this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v)); + private async compileOutputFieldsTemplates(record: any) { + return await this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v)); } - private compileOutputFieldsTemplatesNoImage(record: any) { - return this.compileTemplates(this.options.fillPlainFields, record, v => String(v)); + private async compileOutputFieldsTemplatesNoImage(record: any) { + return await this.compileTemplates(this.options.fillPlainFields, record, v => String(v)); } - private compileGenerationFieldTemplates(record: any) { - return this.compileTemplates(this.options.generateImages, record, v => String(v.prompt)); + private async compileGenerationFieldTemplates(record: any) { + return await this.compileTemplates(this.options.generateImages, record, v => String(v.prompt)); } private async checkRateLimit(field: string, fieldNameRateLimit: string | undefined, headers: Record): Promise { @@ -102,7 +106,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { return { ok: false, error: 'One of the image URLs is not valid' }; } //create prompt for OpenAI - const compiledOutputFields = this.compileOutputFieldsTemplates(record); + const compiledOutputFields = await this.compileOutputFieldsTemplates(record); const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number. @@ -159,7 +163,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey); const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] ); - const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record); + const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record); const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. @@ -208,7 +212,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } } const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => { - const prompt = this.compileGenerationFieldTemplates(record)[key]; + const prompt = (await this.compileGenerationFieldTemplates(record))[key]; let images; if (this.options.attachFiles && attachmentFiles.length === 0) { isError = true; @@ -489,7 +493,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } } } - if (this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) { + if ((this.options.fillFieldsFromImages || this.options.fillPlainFields || this.options.generateImages) && !this.options.provideAdditionalContextForRecord) { let matches: string[] = []; const regex = /{{(.*?)}}/g; @@ -656,7 +660,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { handler: async ({ body, headers }) => { const Id = body.recordId || []; const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]); - const compiledGenerationOptions = this.compileGenerationFieldTemplates(record); + const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record); return { generationOptions: compiledGenerationOptions }; } }); diff --git a/types.ts b/types.ts index aaf4deb..a5c3814 100644 --- a/types.ts +++ b/types.ts @@ -1,5 +1,4 @@ -import { ImageVisionAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth"; - +import AdminForth, { ImageVisionAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth"; export interface PluginOptions { /** @@ -109,4 +108,13 @@ export interface PluginOptions { ok: boolean; error?: undefined; }> + + /** + * Custom message for the context shown to the user when performing the action + */ + provideAdditionalContextForRecord?: ({record, adminUser, resource}: { + record: any; + adminUser: any; + resource: any; + }) => Record | Promise>; } From 2b2c6f673de5270a5f93734ab54cd6fa4fcafdc0 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Fri, 14 Nov 2025 18:35:53 +0200 Subject: [PATCH 2/6] feat: add image generation prompts support and enhance dialog functionality[1] --- custom/ImageGenerationCarousel.vue | 13 ++- custom/VisionAction.vue | 152 ++++++++++++++++++++++++++--- custom/VisionTable.vue | 2 + index.ts | 58 +++++++---- types.ts | 2 + 5 files changed, 192 insertions(+), 35 deletions(-) diff --git a/custom/ImageGenerationCarousel.vue b/custom/ImageGenerationCarousel.vue index ff90029..78b5040 100644 --- a/custom/ImageGenerationCarousel.vue +++ b/custom/ImageGenerationCarousel.vue @@ -143,7 +143,7 @@ const sliderRef = ref(null) const prompt = ref(''); const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']); -const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage']); +const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage', 'imageGenerationPrompts']); const images = ref([]); const loading = ref(false); const attachmentFiles = ref([]) @@ -212,12 +212,21 @@ async function getHistoricalAverage() { } async function getGenerationPrompt() { + console.log('Getting generation prompts imageGenerationPrompts:', props.imageGenerationPrompts); + const [key, ...rest] = props.imageGenerationPrompts.split(":"); + const value = rest.join(":").trim(); + + const json = { + [key.trim()]: value + }; + try{ const resp = await callAdminForthApi({ - path: `/plugin/${props.meta.pluginInstanceId}/get_generation_prompts`, + path: `/plugin/${props.meta.pluginInstanceId}/get_image_generation_prompts`, method: 'POST', body: { recordId: props.recordId, + customPrompt: JSON.stringify(json) || {}, }, }); if(!resp) { diff --git a/custom/VisionAction.vue b/custom/VisionAction.vue index 3528066..ea5aa45 100644 --- a/custom/VisionAction.vue +++ b/custom/VisionAction.vue @@ -8,16 +8,41 @@
-
+
+
+
+

{{ formatKey(key) }}

+
+ {{ formatLabel(promptKey) }} prompt: + +

reset to default

+
+
+
+
+ + +

{{ errorMessage }}

@@ -56,7 +108,7 @@ \ No newline at end of file diff --git a/custom/VisionTable.vue b/custom/VisionTable.vue index 17932a6..b047698 100644 --- a/custom/VisionTable.vue +++ b/custom/VisionTable.vue @@ -173,6 +173,7 @@ :carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]" :regenerateImagesRefreshRate="regenerateImagesRefreshRate" :sourceImage="item.images && item.images.length ? item.images : null" + :imageGenerationPrompts="imageGenerationPrompts[n]" @error="handleError" @close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false" @selectImage="updateSelectedImage" @@ -231,6 +232,7 @@ const props = defineProps<{ imageGenerationErrorMessage: string[], oldData: any[], isImageHasPreviewUrl: Record + imageGenerationPrompts: Record }>(); const emit = defineEmits(['error', 'regenerateImages']); diff --git a/index.ts b/index.ts index 6706e50..c0b4a86 100644 --- a/index.ts +++ b/index.ts @@ -6,7 +6,7 @@ import Handlebars from 'handlebars'; import { RateLimiter } from "adminforth"; import { randomUUID } from "crypto"; -const STUB_MODE = false; +const STUB_MODE = true; const jobs = new Map(); export default class BulkAiFlowPlugin extends AdminForthPlugin { options: PluginOptions; @@ -47,16 +47,18 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { return compiled; } - private async compileOutputFieldsTemplates(record: any) { - return await this.compileTemplates(this.options.fillFieldsFromImages, record, v => String(v)); + private async compileOutputFieldsTemplates(record: any, customPrompt? : string) { + return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) :this.options.fillFieldsFromImages, record, v => String(v)); } - private async compileOutputFieldsTemplatesNoImage(record: any) { - return await this.compileTemplates(this.options.fillPlainFields, record, v => String(v)); + private async compileOutputFieldsTemplatesNoImage(record: any, customPrompt? : string) { + return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.fillPlainFields, record, v => String(v)); } - private async compileGenerationFieldTemplates(record: any) { - return await this.compileTemplates(this.options.generateImages, record, v => String(v.prompt)); + private async compileGenerationFieldTemplates(record: any, customPrompt? : string) { + console.log('compileGenerationFieldTemplates customPrompt:', JSON.parse(customPrompt)); + console.log('TEST RECORD', record) + return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt)); } private async checkRateLimit(field: string, fieldNameRateLimit: string | undefined, headers: Record): Promise { @@ -76,7 +78,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } } - private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record) { + private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record, customPrompt? : string) { const selectedId = recordId; let isError = false; // Fetch the record using the provided ID @@ -106,7 +108,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { return { ok: false, error: 'One of the image URLs is not valid' }; } //create prompt for OpenAI - const compiledOutputFields = await this.compileOutputFieldsTemplates(record); + const compiledOutputFields = await this.compileOutputFieldsTemplates(record, customPrompt); + console.log('Compiled output fields for analysis:', compiledOutputFields); const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number. @@ -152,7 +155,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } - private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record) { + private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record, customPrompt? : string) { const selectedId = recordId; let isError = false; if (STUB_MODE) { @@ -163,7 +166,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey); const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] ); - const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record); + const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record, customPrompt); + console.log('Compiled output fields for analysis:', compiledOutputFields); const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. @@ -192,7 +196,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } } - private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record) { + private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record, customPrompt? : string) { const selectedId = recordId; let isError = false; const start = +new Date(); @@ -212,7 +216,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } } const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => { - const prompt = (await this.compileGenerationFieldTemplates(record))[key]; + const prompt = (await this.compileGenerationFieldTemplates(record, customPrompt))[key]; + console.log('Compiled output fields for analysis:', prompt); let images; if (this.options.attachFiles && attachmentFiles.length === 0) { isError = true; @@ -395,6 +400,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { }; const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey); + const test1 = {customPrompt: "Generate me an anime girl driving tank. Test field: {{user.email}}"} + const test2 = {email: "Aboba"}; + const re2323s = await this.compileTemplates(test1, test2, v => String(test1 ? v : v.prompt)); + console.log('Test compileTemplates result:', re2323s); const pageInjection = { file: this.componentPath('VisionAction.vue'), @@ -417,6 +426,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { fillPlainFields: this.options.refreshRates?.fillPlainFields || 1_000, generateImages: this.options.refreshRates?.generateImages || 5_000, regenerateImages: this.options.refreshRates?.regenerateImages || 5_000, + }, + askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false, + generationPrompts: { + plainFieldsPrompts: this.options.fillPlainFields || {}, + imageFieldsPrompts: this.options.fillFieldsFromImages || {}, + imageGenerationPrompts: this.options.generateImages || {}, } } } @@ -656,11 +671,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { server.endpoint({ method: 'POST', - path: `/plugin/${this.pluginInstanceId}/get_generation_prompts`, + path: `/plugin/${this.pluginInstanceId}/get_image_generation_prompts`, handler: async ({ body, headers }) => { const Id = body.recordId || []; + const customPrompt = body.customPrompt || null; const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]); - const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record); + const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record, customPrompt); + console.log('Compiled generation options:', compiledGenerationOptions); return { generationOptions: compiledGenerationOptions }; } }); @@ -683,10 +700,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { method: 'POST', path: `/plugin/${this.pluginInstanceId}/create-job`, handler: async ({ body, adminUser, headers }) => { - const { actionType, recordId } = body; + const { actionType, recordId, customPrompt } = body; const jobId = randomUUID(); jobs.set(jobId, { status: "in_progress" }); - if (!actionType) { jobs.set(jobId, { status: "failed", error: "Missing action type" }); //return { error: "Missing action type" }; @@ -697,20 +713,20 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } else { switch(actionType) { case 'generate_images': - this.initialImageGenerate(jobId, recordId, adminUser, headers); + this.initialImageGenerate(jobId, recordId, adminUser, headers, customPrompt); break; case 'analyze_no_images': - this.analyzeNoImages(jobId, recordId, adminUser, headers); + this.analyzeNoImages(jobId, recordId, adminUser, headers, customPrompt); break; case 'analyze': - this.analyze_image(jobId, recordId, adminUser, headers); + this.analyze_image(jobId, recordId, adminUser, headers, customPrompt); break; case 'regenerate_images': if (!body.prompt || !body.fieldName) { jobs.set(jobId, { status: "failed", error: "Missing prompt or field name" }); break; } - this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers); + this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers, customPrompt); break; default: jobs.set(jobId, { status: "failed", error: "Unknown action type" }); diff --git a/types.ts b/types.ts index a5c3814..b823dab 100644 --- a/types.ts +++ b/types.ts @@ -117,4 +117,6 @@ export interface PluginOptions { adminUser: any; resource: any; }) => Record | Promise>; + + askConfirmationBeforeGenerating?: boolean; } From 3702ae86b611a6dffff30e5b8d97cf22a17b9bdd Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Mon, 17 Nov 2025 10:28:43 +0200 Subject: [PATCH 3/6] fix: update prompt handling and improve error messaging in image generation components --- custom/ImageGenerationCarousel.vue | 7 ++- custom/VisionAction.vue | 81 +++++++++++++++++++++--------- index.ts | 9 ++-- 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/custom/ImageGenerationCarousel.vue b/custom/ImageGenerationCarousel.vue index 78b5040..52875b2 100644 --- a/custom/ImageGenerationCarousel.vue +++ b/custom/ImageGenerationCarousel.vue @@ -154,7 +154,7 @@ onMounted(async () => { } const temp = await getGenerationPrompt() || ''; attachmentFiles.value = props.sourceImage || []; - prompt.value = temp[props.fieldName]; + prompt.value = Object.keys(JSON.parse(temp))[0]; await nextTick(); const currentIndex = props.carouselImageIndex || 0; @@ -212,7 +212,6 @@ async function getHistoricalAverage() { } async function getGenerationPrompt() { - console.log('Getting generation prompts imageGenerationPrompts:', props.imageGenerationPrompts); const [key, ...rest] = props.imageGenerationPrompts.split(":"); const value = rest.join(":").trim(); @@ -220,7 +219,7 @@ async function getGenerationPrompt() { [key.trim()]: value }; - try{ + try { const resp = await callAdminForthApi({ path: `/plugin/${props.meta.pluginInstanceId}/get_image_generation_prompts`, method: 'POST', @@ -235,7 +234,7 @@ async function getGenerationPrompt() { errorMessage: "Error getting generation prompts." }); } - return resp?.generationOptions || null; + return resp?.prompt || null; } catch (e) { emit('error', { isError: true, diff --git a/custom/VisionAction.vue b/custom/VisionAction.vue index ea5aa45..42f3dad 100644 --- a/custom/VisionAction.vue +++ b/custom/VisionAction.vue @@ -38,10 +38,18 @@ onclick: (dialog) => { saveSettings(); } }, ] : - []" + [ + { + label: 'Edit prompts', + options: { + class: 'w-fit ml-auto' + }, + onclick: (dialog) => { clickSettingsButton(); } + }, + ]" :click-to-close-outside="false" > -
+
+
+

{{ errorMessage }}

+
-
+

{{ formatKey(key) }}

-
- {{ formatLabel(promptKey) }} prompt: - -

reset to default

+
+
+ {{ formatLabel(promptKey) }} prompt: + +

reset to default

+
- -
-
-

{{ errorMessage }}

-
@@ -835,12 +842,6 @@ function saveSettings() { async function getGenerationPrompts() { const calculatedGenerationPrompts: any = {}; const savedPrompts = localStorage.getItem(`bulkAiFlowGenerationPrompts_${props.meta.pluginInstanceId}`); - if (savedPrompts) { - generationPrompts - generationPrompts.value = JSON.parse(savedPrompts); - - return;; - } if (props.meta.generationPrompts.plainFieldsPrompts) { calculatedGenerationPrompts.plainFieldsPrompts = props.meta.generationPrompts.plainFieldsPrompts; } @@ -855,7 +856,12 @@ async function getGenerationPrompts() { } calculatedGenerationPrompts.generateImages = imageFields; } - console.log('calculatedGenerationPrompts', calculatedGenerationPrompts); + if (savedPrompts) { + + generationPrompts.value = checkAndAddNewFieldsToPrompts(JSON.parse(savedPrompts), calculatedGenerationPrompts); + + return;; + } generationPrompts.value = calculatedGenerationPrompts; } @@ -881,4 +887,31 @@ function formatKey(str) { .join(' '); } +function checkAndAddNewFieldsToPrompts(savedPrompts, defaultPrompts) { + for (const categoryKey in defaultPrompts) { + if (!savedPrompts.hasOwnProperty(categoryKey)) { + savedPrompts[categoryKey] = defaultPrompts[categoryKey]; + } else { + for (const promptKey in defaultPrompts[categoryKey]) { + if (!savedPrompts[categoryKey].hasOwnProperty(promptKey)) { + savedPrompts[categoryKey][promptKey] = defaultPrompts[categoryKey][promptKey]; + } + } + } + } + //remove deprecated fields + for (const categoryKey in savedPrompts) { + if (!defaultPrompts.hasOwnProperty(categoryKey)) { + delete savedPrompts[categoryKey]; + } else { + for (const promptKey in savedPrompts[categoryKey]) { + if (!defaultPrompts[categoryKey].hasOwnProperty(promptKey)) { + delete savedPrompts[categoryKey][promptKey]; + } + } + } + } + return savedPrompts; +} + \ No newline at end of file diff --git a/index.ts b/index.ts index c0b4a86..0716889 100644 --- a/index.ts +++ b/index.ts @@ -6,7 +6,7 @@ import Handlebars from 'handlebars'; import { RateLimiter } from "adminforth"; import { randomUUID } from "crypto"; -const STUB_MODE = true; +const STUB_MODE = false; const jobs = new Map(); export default class BulkAiFlowPlugin extends AdminForthPlugin { options: PluginOptions; @@ -676,9 +676,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { const Id = body.recordId || []; const customPrompt = body.customPrompt || null; const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]); - const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record, customPrompt); - console.log('Compiled generation options:', compiledGenerationOptions); - return { generationOptions: compiledGenerationOptions }; + const compiledGenerationOptions = await this.compileGenerationFieldTemplates(record, JSON.stringify({"prompt": customPrompt})); + return compiledGenerationOptions; } }); @@ -726,7 +725,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { jobs.set(jobId, { status: "failed", error: "Missing prompt or field name" }); break; } - this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers, customPrompt); + this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers); break; default: jobs.set(jobId, { status: "failed", error: "Unknown action type" }); From 62dfa3bdc7795c5d5d0148aae21e7e3af8355e20 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Thu, 20 Nov 2025 10:08:19 +0200 Subject: [PATCH 4/6] fix: improve prompt labels for clarity and remove unused formatKey function --- custom/VisionAction.vue | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/custom/VisionAction.vue b/custom/VisionAction.vue index 42f3dad..624ca53 100644 --- a/custom/VisionAction.vue +++ b/custom/VisionAction.vue @@ -89,14 +89,18 @@ :key="key" class="w-full" > -
-

{{ formatKey(key) }}

+
+

{{ + key === "plainFieldsPrompts" ? "Prompts for non-image fields" + : key === "generateImages" ? "Prompts for image fields" + : "Prompts for image analysis" + }}

{{ formatLabel(promptKey) }} prompt:

reset to default

@@ -878,14 +882,6 @@ function clickSettingsButton() { popupMode.value = 'settings'; } -function formatKey(str) { - return str - .replace(/([A-Z])/g, ' $1') - .trim() - .split(' ') - .map(w => w.charAt(0).toUpperCase() + w.slice(1)) - .join(' '); -} function checkAndAddNewFieldsToPrompts(savedPrompts, defaultPrompts) { for (const categoryKey in defaultPrompts) { From 613a29cc639b99c6b28226bae63603bdfaac5348 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Thu, 20 Nov 2025 11:09:50 +0200 Subject: [PATCH 5/6] fix: remove unnecessary console logs in template compilation methods --- custom/VisionAction.vue | 2 +- index.ts | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/custom/VisionAction.vue b/custom/VisionAction.vue index 624ca53..d0e8f87 100644 --- a/custom/VisionAction.vue +++ b/custom/VisionAction.vue @@ -864,7 +864,7 @@ async function getGenerationPrompts() { generationPrompts.value = checkAndAddNewFieldsToPrompts(JSON.parse(savedPrompts), calculatedGenerationPrompts); - return;; + return; } generationPrompts.value = calculatedGenerationPrompts; } diff --git a/index.ts b/index.ts index 0716889..7956abd 100644 --- a/index.ts +++ b/index.ts @@ -56,8 +56,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } private async compileGenerationFieldTemplates(record: any, customPrompt? : string) { - console.log('compileGenerationFieldTemplates customPrompt:', JSON.parse(customPrompt)); - console.log('TEST RECORD', record) return await this.compileTemplates(customPrompt ? JSON.parse(customPrompt) : this.options.generateImages, record, v => String(customPrompt ? v : v.prompt)); } @@ -109,7 +107,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } //create prompt for OpenAI const compiledOutputFields = await this.compileOutputFieldsTemplates(record, customPrompt); - console.log('Compiled output fields for analysis:', compiledOutputFields); const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number. @@ -167,7 +164,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, selectedId)] ); const compiledOutputFields = await this.compileOutputFieldsTemplatesNoImage(record, customPrompt); - console.log('Compiled output fields for analysis:', compiledOutputFields); const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}. Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON. Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. @@ -217,7 +213,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { } const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => { const prompt = (await this.compileGenerationFieldTemplates(record, customPrompt))[key]; - console.log('Compiled output fields for analysis:', prompt); let images; if (this.options.attachFiles && attachmentFiles.length === 0) { isError = true; @@ -400,11 +395,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin { }; const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey); - const test1 = {customPrompt: "Generate me an anime girl driving tank. Test field: {{user.email}}"} - const test2 = {email: "Aboba"}; - const re2323s = await this.compileTemplates(test1, test2, v => String(test1 ? v : v.prompt)); - console.log('Test compileTemplates result:', re2323s); - + const pageInjection = { file: this.componentPath('VisionAction.vue'), meta: { From 443e91d1b8028c4d0d6aa9e81c3067f308ca0f19 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Thu, 20 Nov 2025 11:22:48 +0200 Subject: [PATCH 6/6] fix: add confirmation check before generating prompts based on props --- custom/VisionAction.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/VisionAction.vue b/custom/VisionAction.vue index d0e8f87..26c32f7 100644 --- a/custom/VisionAction.vue +++ b/custom/VisionAction.vue @@ -860,7 +860,7 @@ async function getGenerationPrompts() { } calculatedGenerationPrompts.generateImages = imageFields; } - if (savedPrompts) { + if (savedPrompts && props.meta.askConfirmationBeforeGenerating) { generationPrompts.value = checkAndAddNewFieldsToPrompts(JSON.parse(savedPrompts), calculatedGenerationPrompts);