Skip to content

Commit 8884a74

Browse files
authored
Merge pull request #6 from devforth/feature/AdminForth/995/add-"messageforcont-and-abilit
Feature/admin forth/995/add "messageforcont and abilit
2 parents 0dcdb62 + 443e91d commit 8884a74

File tree

5 files changed

+233
-46
lines changed

5 files changed

+233
-46
lines changed

custom/ImageGenerationCarousel.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const sliderRef = ref(null)
143143
144144
const prompt = ref('');
145145
const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
146-
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage']);
146+
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex', 'regenerateImagesRefreshRate','sourceImage', 'imageGenerationPrompts']);
147147
const images = ref([]);
148148
const loading = ref(false);
149149
const attachmentFiles = ref<string[]>([])
@@ -154,7 +154,7 @@ onMounted(async () => {
154154
}
155155
const temp = await getGenerationPrompt() || '';
156156
attachmentFiles.value = props.sourceImage || [];
157-
prompt.value = temp[props.fieldName];
157+
prompt.value = Object.keys(JSON.parse(temp))[0];
158158
await nextTick();
159159
160160
const currentIndex = props.carouselImageIndex || 0;
@@ -212,12 +212,20 @@ async function getHistoricalAverage() {
212212
}
213213
214214
async function getGenerationPrompt() {
215-
try{
215+
const [key, ...rest] = props.imageGenerationPrompts.split(":");
216+
const value = rest.join(":").trim();
217+
218+
const json = {
219+
[key.trim()]: value
220+
};
221+
222+
try {
216223
const resp = await callAdminForthApi({
217-
path: `/plugin/${props.meta.pluginInstanceId}/get_generation_prompts`,
224+
path: `/plugin/${props.meta.pluginInstanceId}/get_image_generation_prompts`,
218225
method: 'POST',
219226
body: {
220227
recordId: props.recordId,
228+
customPrompt: JSON.stringify(json) || {},
221229
},
222230
});
223231
if(!resp) {
@@ -226,7 +234,7 @@ async function getGenerationPrompt() {
226234
errorMessage: "Error getting generation prompts."
227235
});
228236
}
229-
return resp?.generationOptions || null;
237+
return resp?.prompt || null;
230238
} catch (e) {
231239
emit('error', {
232240
isError: true,

custom/VisionAction.vue

Lines changed: 172 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,49 @@
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"
@@ -44,10 +77,40 @@
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>
@@ -56,7 +119,7 @@
56119
<script lang="ts" setup>
57120
import { callAdminForthApi } from '@/utils';
58121
import { Ref, ref, watch } from 'vue'
59-
import { Dialog, Button } from '@/afcl';
122+
import { Dialog, Button, Textarea } from '@/afcl';
60123
import VisionTable from './VisionTable.vue'
61124
import adminforth from '@/adminforth';
62125
import { useI18n } from 'vue-i18n';
@@ -114,6 +177,8 @@ const aiGenerationErrorMessage = ref<string[]>([]);
114177
const isAiImageGenerationError = ref<boolean[]>([false]);
115178
const imageGenerationErrorMessage = ref<string[]>([]);
116179
const isImageHasPreviewUrl = ref<Record<string, boolean>>({});
180+
const popupMode = ref<'generation' | 'confirmation' | 'settings'>('confirmation');
181+
const generationPrompts = ref<any>({});
117182
118183
const 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+
174250
const 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
191267
watch(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>

custom/VisionTable.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
:carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
174174
:regenerateImagesRefreshRate="regenerateImagesRefreshRate"
175175
:sourceImage="item.images && item.images.length ? item.images : null"
176+
:imageGenerationPrompts="imageGenerationPrompts[n]"
176177
@error="handleError"
177178
@close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
178179
@selectImage="updateSelectedImage"
@@ -231,6 +232,7 @@ const props = defineProps<{
231232
imageGenerationErrorMessage: string[],
232233
oldData: any[],
233234
isImageHasPreviewUrl: Record<string, boolean>
235+
imageGenerationPrompts: Record<string, any>
234236
}>();
235237
const emit = defineEmits(['error', 'regenerateImages']);
236238

0 commit comments

Comments
 (0)