88 <Dialog
99 ref =" confirmDialog"
1010 header =" Bulk AI Flow"
11- class =" [scrollbar-gutter:stable] !max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
11+ class =" [scrollbar-gutter:stable] !max-w-full w-fit h-fit"
12+ :class =" popupMode === 'generation' ? 'lg:w-[1600px] !lg:max-w-[1600px]'
13+ : popupMode === 'settings' ? 'lg:w-[1000px] !lg:max-w-[1000px]'
14+ : 'lg:w-[500px] !lg:max-w-[500px]'"
1215 :beforeCloseFunction =" closeDialog"
13- :buttons =" [
14- { label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages, loader: isLoading, class: 'w-fit' }, onclick: async (dialog) => { await saveData(); dialog.hide(); } },
15- { label: 'Cancel', options: {class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200'}, onclick: (dialog) => dialog.hide() },
16- ]"
16+ :buttons =" popupMode === 'generation' ? [
17+ {
18+ label: checkedCount > 1 ? 'Save fields' : 'Save field',
19+ options: {
20+ disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords || isGeneratingImages || isAnalizingFields || isAnalizingImages,
21+ loader: isLoading, class: 'w-fit'
22+ },
23+ onclick: async (dialog) => { await saveData(); dialog.hide(); }
24+ },
25+ {
26+ label: 'Cancel',
27+ options: {
28+ class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200'
29+ },
30+ onclick: (dialog) => dialog.hide()
31+ },
32+ ] : popupMode === 'settings' ? [
33+ {
34+ label: 'Save settings',
35+ options: {
36+ class: 'w-fit'
37+ },
38+ onclick: (dialog) => { saveSettings(); }
39+ },
40+ ] :
41+ [
42+ {
43+ label: 'Edit prompts',
44+ options: {
45+ class: 'w-fit ml-auto'
46+ },
47+ onclick: (dialog) => { clickSettingsButton(); }
48+ },
49+ ]"
1750 :click-to-close-outside =" false"
1851 >
19- <div class =" [scrollbar-gutter:stable] bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[90vh ] gap-3 md:gap-4 w-full h-full overflow-y-auto" >
20- <div v-if =" records && props.checkboxes.length" class =" w-full overflow-x-auto" >
52+ <div class =" [scrollbar-gutter:stable] bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[75vh ] gap-3 md:gap-4 w-full h-full overflow-y-auto" >
53+ <div v-if =" records && props.checkboxes.length && popupMode === 'generation' " class =" w-full overflow-x-auto" >
2154 <VisionTable
2255 :checkbox =" props.checkboxes"
2356 :records =" records"
4477 :imageGenerationErrorMessage =" imageGenerationErrorMessage"
4578 @regenerate-images =" regenerateImages"
4679 :isImageHasPreviewUrl =" isImageHasPreviewUrl"
80+ :imageGenerationPrompts =" generationPrompts.generateImages"
4781 />
82+ <div class =" text-red-600 flex items-center w-full" >
83+ <p v-if =" isError === true" >{{ errorMessage }}</p >
84+ </div >
4885 </div >
49- <div class =" text-red-600 flex items-center w-full" >
50- <p v-if =" isError === true" >{{ errorMessage }}</p >
86+ <div
87+ v-else-if =" popupMode === 'settings'"
88+ v-for =" (promptsCategory, key) in generationPrompts"
89+ :key =" key"
90+ class =" w-full"
91+ >
92+ <div v-if =" Object.keys(promptsCategory).length > 0" class =" gap-4 mb-6 ml-1" >
93+ <p class =" text-start w-full text-xl font-bold mb-2" >{{
94+ key === "plainFieldsPrompts" ? "Prompts for non-image fields"
95+ : key === "generateImages" ? "Prompts for image fields"
96+ : "Prompts for image analysis"
97+ }}</p >
98+ <div class =" grid grid-cols-2 gap-4" >
99+ <div v-for =" (prompt, promptKey) in promptsCategory" :key =" promptKey" >
100+ {{ formatLabel(promptKey) }} prompt:
101+ <Textarea
102+ v-model =" generationPrompts[key][promptKey]"
103+ class =" w-full h-32 p-2 border border-gray-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-purple-500"
104+ ></Textarea >
105+ <p class =" text-red-500 hover:underline hover:cursor-pointer mt-2" @click =" resetPromptToDefault(key, promptKey)" >reset to default</p >
106+ </div >
107+ </div >
108+ </div >
109+ </div >
110+ <div v-else class =" flex flex-col gap-2" >
111+ <Button @click =" runAiActions" class =" px-5 py-2.5 my-20 bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 rounded-md text-white border-none" >
112+ Start generation
113+ </Button >
51114 </div >
52115 </div >
53116 </Dialog >
56119<script lang="ts" setup>
57120import { callAdminForthApi } from ' @/utils' ;
58121import { Ref , ref , watch } from ' vue'
59- import { Dialog , Button } from ' @/afcl' ;
122+ import { Dialog , Button , Textarea } from ' @/afcl' ;
60123import VisionTable from ' ./VisionTable.vue'
61124import adminforth from ' @/adminforth' ;
62125import { useI18n } from ' vue-i18n' ;
@@ -114,6 +177,8 @@ const aiGenerationErrorMessage = ref<string[]>([]);
114177const isAiImageGenerationError = ref <boolean []>([false ]);
115178const imageGenerationErrorMessage = ref <string []>([]);
116179const isImageHasPreviewUrl = ref <Record <string , boolean >>({});
180+ const popupMode = ref <' generation' | ' confirmation' | ' settings' >(' confirmation' );
181+ const generationPrompts = ref <any >({});
117182
118183const openDialog = async () => {
119184 isDialogOpen .value = true ;
@@ -144,8 +209,19 @@ const openDialog = async () => {
144209 },{[primaryKey ]: records .value [i ][primaryKey ]} as Record <string , boolean >);
145210 }
146211 isFetchingRecords .value = false ;
147-
148- if (props .meta .isImageGeneration ) {
212+ // Ensure prompts are loaded before any automatic AI action run
213+ if (! generationPrompts .value || Object .keys (generationPrompts .value ).length === 0 ) {
214+ await getGenerationPrompts ();
215+ }
216+ if (! props .meta .askConfirmationBeforeGenerating ) {
217+ runAiActions ();
218+ }
219+ }
220+
221+
222+ function runAiActions() {
223+ popupMode .value = ' generation' ;
224+ if (props .meta .isImageGeneration ) {
149225 isGeneratingImages .value = true ;
150226 runAiAction ({
151227 endpoint: ' initial_image_generate' ,
@@ -170,9 +246,8 @@ const openDialog = async () => {
170246 });
171247 }
172248}
173-
249+
174250const closeDialog = () => {
175- confirmDialog .value .close ();
176251 isAiResponseReceivedAnalize .value = [];
177252 isAiResponseReceivedImage .value = [];
178253
@@ -186,10 +261,10 @@ const closeDialog = () => {
186261 isImageGenerationError .value = false ;
187262 errorMessage .value = ' ' ;
188263 isDialogOpen .value = false ;
264+ popupMode .value = ' confirmation' ;
189265}
190266
191267watch (selected , (val ) => {
192- // console.log('Selected changed:', val);
193268 checkedCount .value = val .filter (item => item .isChecked === true ).length ;
194269}, { deep: true });
195270
@@ -492,6 +567,15 @@ async function runAiAction({
492567 return ;
493568 };
494569 }
570+
571+ let customPrompt;
572+ if (actionType === ' generate_images' ) {
573+ customPrompt = generationPrompts .value .imageGenerationPrompts || generationPrompts .value .generateImages ;
574+ } else if (actionType === ' analyze' ) {
575+ customPrompt = generationPrompts .value .imageFieldsPrompts ;
576+ } else if (actionType === ' analyze_no_images' ) {
577+ customPrompt = generationPrompts .value .plainFieldsPrompts ;
578+ }
495579 // creating jobs
496580 const tasks = recordsIds .map (async (checkbox , i ) => {
497581 try {
@@ -501,6 +585,7 @@ async function runAiAction({
501585 body: {
502586 actionType: actionType ,
503587 recordId: checkbox ,
588+ ... (customPrompt !== undefined ? { customPrompt: JSON .stringify (customPrompt ) } : {}),
504589 },
505590 });
506591
@@ -753,4 +838,76 @@ function click() {
753838 openDialog ();
754839}
755840
841+ function saveSettings() {
842+ popupMode .value = ' confirmation' ;
843+ localStorage .setItem (` bulkAiFlowGenerationPrompts_${props .meta .pluginInstanceId } ` , JSON .stringify (generationPrompts .value ));
844+ }
845+
846+ async function getGenerationPrompts() {
847+ const calculatedGenerationPrompts: any = {};
848+ const savedPrompts = localStorage .getItem (` bulkAiFlowGenerationPrompts_${props .meta .pluginInstanceId } ` );
849+ if (props .meta .generationPrompts .plainFieldsPrompts ) {
850+ calculatedGenerationPrompts .plainFieldsPrompts = props .meta .generationPrompts .plainFieldsPrompts ;
851+ }
852+ if (props .meta .generationPrompts .imageFieldsPrompts ) {
853+ calculatedGenerationPrompts .imageFieldsPrompts = props .meta .generationPrompts .imageFieldsPrompts ;
854+ }
855+ if (props .meta .generationPrompts .imageGenerationPrompts ) {
856+ let imageFields = {};
857+ for (const [key, value] of Object .entries (props .meta .generationPrompts .imageGenerationPrompts )) {
858+ // value might be typed as unknown; cast to any to access prompt safely
859+ imageFields [key ] = (value as any ).prompt ;
860+ }
861+ calculatedGenerationPrompts .generateImages = imageFields ;
862+ }
863+ if (savedPrompts && props .meta .askConfirmationBeforeGenerating ) {
864+
865+ generationPrompts .value = checkAndAddNewFieldsToPrompts (JSON .parse (savedPrompts ), calculatedGenerationPrompts );
866+
867+ return ;
868+ }
869+ generationPrompts .value = calculatedGenerationPrompts ;
870+ }
871+
872+ function resetPromptToDefault(categoryKey , promptKey ) {
873+ if (categoryKey === ' generateImages' ) {
874+ generationPrompts .value [categoryKey ][promptKey ] = props .meta .generationPrompts .imageGenerationPrompts [promptKey ].prompt ;
875+ return ;
876+ }
877+ generationPrompts .value [categoryKey ][promptKey ] = props .meta .generationPrompts [categoryKey ][promptKey ];
878+ }
879+
880+ function clickSettingsButton() {
881+ getGenerationPrompts ();
882+ popupMode .value = ' settings' ;
883+ }
884+
885+
886+ function checkAndAddNewFieldsToPrompts(savedPrompts , defaultPrompts ) {
887+ for (const categoryKey in defaultPrompts ) {
888+ if (! savedPrompts .hasOwnProperty (categoryKey )) {
889+ savedPrompts [categoryKey ] = defaultPrompts [categoryKey ];
890+ } else {
891+ for (const promptKey in defaultPrompts [categoryKey ]) {
892+ if (! savedPrompts [categoryKey ].hasOwnProperty (promptKey )) {
893+ savedPrompts [categoryKey ][promptKey ] = defaultPrompts [categoryKey ][promptKey ];
894+ }
895+ }
896+ }
897+ }
898+ // remove deprecated fields
899+ for (const categoryKey in savedPrompts ) {
900+ if (! defaultPrompts .hasOwnProperty (categoryKey )) {
901+ delete savedPrompts [categoryKey ];
902+ } else {
903+ for (const promptKey in savedPrompts [categoryKey ]) {
904+ if (! defaultPrompts [categoryKey ].hasOwnProperty (promptKey )) {
905+ delete savedPrompts [categoryKey ][promptKey ];
906+ }
907+ }
908+ }
909+ }
910+ return savedPrompts ;
911+ }
912+
756913 </script >
0 commit comments