@adminforth/bulk-ai-flow 1.25.1 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log +2 -2
- package/custom/ImageGenerationCarousel.vue +106 -147
- package/custom/Swiper.vue +1 -1
- package/custom/VisionAction.vue +336 -109
- package/custom/VisionTable.vue +382 -222
- package/dist/custom/ImageGenerationCarousel.vue +106 -147
- package/dist/custom/Swiper.vue +1 -1
- package/dist/custom/VisionAction.vue +336 -109
- package/dist/custom/VisionTable.vue +382 -222
- package/dist/index.js +2 -1
- package/index.ts +2 -3
- package/package.json +3 -3
- package/types.ts +6 -0
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex items-end justify-start gap-2 cursor-pointer">
|
|
3
|
-
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-
|
|
3
|
+
<div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-lightPrimary via-lightPrimary/90 to-lightPrimary/80 hover:opacity-90 focus:ring-4 focus:outline-none focus:ring-lightPrimary/30 dark:focus:ring-darkPrimary/30 font-medium rounded-md text-sm px-1 text-center dark:bg-none dark:bg-darkPrimary/10 dark:text-darkPrimary dark:brightness-200">
|
|
4
4
|
{{t('AI')}}
|
|
5
5
|
</div>
|
|
6
6
|
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
|
|
7
7
|
</div>
|
|
8
|
-
<Dialog
|
|
8
|
+
<Dialog
|
|
9
9
|
ref="confirmDialog"
|
|
10
|
-
header="Bulk AI Generation"
|
|
11
|
-
class="[scrollbar-gutter:stable]
|
|
10
|
+
:header="t('Bulk AI Generation')"
|
|
11
|
+
class="[scrollbar-gutter:stable] max-w-full h-fit"
|
|
12
|
+
:beforeCancelFunction="handleBeforeCancel"
|
|
12
13
|
:class="popupMode === 'generation' ? 'lg:w-auto !lg:max-w-[1600px]'
|
|
13
14
|
: popupMode === 'settings' ? 'lg:w-[1000px] !lg:max-w-[1000px]'
|
|
14
15
|
: 'lg:w-[500px] !lg:max-w-[500px]'"
|
|
15
|
-
:
|
|
16
|
-
:closable="false"
|
|
16
|
+
:closable="true"
|
|
17
17
|
:buttons="popupMode === 'generation' ? generationModeButtons : popupMode === 'settings' ? [
|
|
18
|
+
{
|
|
19
|
+
label: t('Cancel'),
|
|
20
|
+
options: {
|
|
21
|
+
class: 'afcl-button w-1/6',
|
|
22
|
+
mode: 'secondary'
|
|
23
|
+
},
|
|
24
|
+
onclick: () => { popupMode = 'confirmation'; }
|
|
25
|
+
},
|
|
18
26
|
{
|
|
19
27
|
label: t('Save settings'),
|
|
20
28
|
options: {
|
|
21
|
-
class: 'w-
|
|
29
|
+
class: 'afcl-button w-1/6',
|
|
22
30
|
},
|
|
23
31
|
onclick: (dialog) => { saveSettings(); }
|
|
24
32
|
},
|
|
@@ -27,29 +35,36 @@
|
|
|
27
35
|
{
|
|
28
36
|
label: t('Cancel'),
|
|
29
37
|
options: {
|
|
30
|
-
class: 'w-2
|
|
38
|
+
class: 'afcl-button w-1/2',
|
|
39
|
+
mode: 'secondary'
|
|
31
40
|
},
|
|
32
41
|
onclick: (dialog) => confirmDialog.tryToHideModal()
|
|
33
42
|
},
|
|
34
43
|
{
|
|
35
44
|
label: t('Start generation'),
|
|
36
45
|
options: {
|
|
37
|
-
class: 'w-
|
|
46
|
+
class: 'afcl-button w-1/2',
|
|
38
47
|
loader: isCheckingRateLimits
|
|
39
48
|
},
|
|
40
|
-
onclick: (
|
|
49
|
+
onclick: async () => { await runAiActions(); }
|
|
41
50
|
}
|
|
42
51
|
]"
|
|
43
52
|
:click-to-close-outside="false"
|
|
44
53
|
>
|
|
45
|
-
<div
|
|
54
|
+
<div
|
|
55
|
+
class="bulk-vision-table flex flex-col gap-3 w-full"
|
|
56
|
+
:class="{
|
|
57
|
+
'h-[830px]': popupMode === 'generation',
|
|
58
|
+
'h-auto max-h-[720px]': popupMode !== 'generation'
|
|
59
|
+
}"
|
|
60
|
+
>
|
|
46
61
|
<template v-if="recordsList.length && popupMode === 'generation'" >
|
|
47
|
-
<div class="w-full">
|
|
48
|
-
<p :class="isGenerationPaused ? '' : 'opacity-0'" class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
62
|
+
<div class="w-full min-w-[980px]">
|
|
49
63
|
<div v-if="isGenerationPaused" class="flex flex-col gap-2 mb-2">
|
|
64
|
+
<p class="text-sm font-semibold text-yellow-800">{{ t(`Generated ${startedRecordCount} records. `) + t('Generation on pause. Resume generation?') }}</p>
|
|
50
65
|
<div class="flex items-center gap-2">
|
|
51
66
|
<button
|
|
52
|
-
class="h-8 px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-
|
|
67
|
+
class="h-8 px-3 py-1.5 text-sm rounded-md bg-gradient-to-r from-lightPrimary to-lightPrimary/80 text-white border-none ml-2"
|
|
53
68
|
@click="resumeGeneration"
|
|
54
69
|
>
|
|
55
70
|
{{ t('Resume generation') }}
|
|
@@ -62,36 +77,59 @@
|
|
|
62
77
|
</button>
|
|
63
78
|
</div>
|
|
64
79
|
</div>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class="
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
|
|
81
|
+
<div class="generation-progress-panel w-full bg-white dark:bg-gray-900 border border-gray-200/80 dark:border-gray-800 p-5 m-1 rounded-2xl shadow-sm flex flex-col gap-4">
|
|
82
|
+
<div class="generation-status-header flex items-center justify-between">
|
|
83
|
+
<div class="flex items-center gap-3">
|
|
84
|
+
<div
|
|
85
|
+
class="p-2 rounded-xl shrink-0"
|
|
86
|
+
:class="generationStatusClass"
|
|
87
|
+
>
|
|
88
|
+
<IconShieldCheckOutline v-if="displayedProcessedCount === totalRecords" class="w-5 h-5" />
|
|
89
|
+
<IconRefreshOutline v-else :class="isProcessingAny ? 'animate-spin' : ''" class="w-5 h-5" />
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div>
|
|
93
|
+
<h4 class="text-sm font-bold text-gray-800 dark:text-gray-200 tracking-tight">
|
|
94
|
+
{{ displayedProcessedCount }} {{ t('of') }} {{ totalRecords }} {{ t('fields ready to save') }}
|
|
95
|
+
</h4>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<span
|
|
100
|
+
class="px-2.5 py-1 text-xs rounded-default border flex items-center gap-1.5"
|
|
101
|
+
:class="generationStatusClass"
|
|
102
|
+
>
|
|
103
|
+
<span
|
|
104
|
+
class="w-1.5 h-1.5 rounded-full"
|
|
105
|
+
:class="isGenerationPaused ? 'bg-yellow-500' : displayedProcessedCount === totalRecords ? 'bg-green-500' : 'bg-blue-500'"
|
|
106
|
+
></span>
|
|
107
|
+
{{
|
|
108
|
+
isGenerationPaused ? t('On pause')
|
|
109
|
+
: displayedProcessedCount === totalRecords ? t('Ready')
|
|
110
|
+
: t('Processing')
|
|
111
|
+
}}
|
|
112
|
+
</span>
|
|
88
113
|
</div>
|
|
114
|
+
|
|
115
|
+
<ProgressBar
|
|
116
|
+
class="progress-bar w-full"
|
|
117
|
+
:class="isGenerationPaused ? 'opacity-80' : ''"
|
|
118
|
+
:current-value="Math.floor((displayedProcessedCount / totalRecords) * 100)"
|
|
119
|
+
:max-value="100"
|
|
120
|
+
:min-value="0"
|
|
121
|
+
:showAnimation="isProcessingAny && !isGenerationPaused"
|
|
122
|
+
:showLabels="false"
|
|
123
|
+
:showValues="false"
|
|
124
|
+
:show-progress="false"
|
|
125
|
+
/>
|
|
126
|
+
|
|
89
127
|
</div>
|
|
90
128
|
</div>
|
|
91
129
|
|
|
92
130
|
|
|
93
131
|
<VisionTable
|
|
94
|
-
class="
|
|
132
|
+
class="w-full flex-1 min-h-0 overflow-y-auto [scrollbar-gutter:stable] [mask-image:linear-gradient(to_bottom,transparent_0px,black_20px)]"
|
|
95
133
|
ref="tableRef"
|
|
96
134
|
:records="recordsList"
|
|
97
135
|
:meta="props.meta"
|
|
@@ -105,62 +143,167 @@
|
|
|
105
143
|
:outputImageFields="props.meta.outputImageFields"
|
|
106
144
|
:outputFieldsForAnalizeFromImages="props.meta.outputFieldsForAnalizeFromImages"
|
|
107
145
|
:outputPlainFields="props.meta.outputPlainFields"
|
|
146
|
+
:overwriteExistingValues="overwriteExistingValues"
|
|
108
147
|
@error="handleTableError"
|
|
109
148
|
@regenerate-images="regenerateImages"
|
|
110
|
-
@regenerate-
|
|
149
|
+
@regenerate-record="regenerateRecord"
|
|
111
150
|
/>
|
|
112
|
-
<div class="
|
|
151
|
+
<div class="mt-2 flex items-center text-lightPrimary dark:text-darkPrimary bg-lightPrimary/10 dark:bg-darkPrimary/10 px-4 py-3 rounded-default border border-lightPrimary/20 dark:border-darkPrimary/20 text-sm">
|
|
152
|
+
<IconInfoCircleSolid class="w-5 h-5 me-2 shrink-0 text-lightPrimary dark:text-darkPrimary dark:brightness-200" />
|
|
153
|
+
<p class="text-lightPrimary dark:text-darkPrimary dark:brightness-200">
|
|
154
|
+
<span class="font-semibold text-lightPrimary dark:text-darkPrimary dark:brightness-200">{{ t('Only checked fields will be saved.') }}</span>
|
|
155
|
+
{{ t('You can regenerate fields before saving if needed.') }}
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="af-error-message flex items-center w-full text-red-600">
|
|
113
159
|
<p v-if="isError === true">{{ errorMessage }}</p>
|
|
114
160
|
</div>
|
|
115
161
|
</template>
|
|
116
162
|
<template v-else-if="!recordsList.length && popupMode === 'generation'">
|
|
117
|
-
<p>No data to save. Feel free to click "Cancel"</p>
|
|
163
|
+
<p>{{ t('No data to save. Feel free to click "Cancel"') }}</p>
|
|
118
164
|
</template>
|
|
119
|
-
<div
|
|
120
|
-
v-
|
|
121
|
-
v-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
165
|
+
<div v-else-if="popupMode === 'settings'" class="af-settings-panel w-full flex flex-col gap-6 overflow-y-auto overflow-x-hidden pr-2 max-h-[70vh]">
|
|
166
|
+
<template v-for="(promptsCategory, key) in generationPrompts" :key="key">
|
|
167
|
+
<div v-if="Object.keys(promptsCategory).length > 0" class="w-full flex flex-col">
|
|
168
|
+
<div class="flex items-start gap-3.5 mb-6">
|
|
169
|
+
|
|
170
|
+
<div class="flex-1 min-w-0">
|
|
171
|
+
<h3 class="text-base font-bold text-gray-900 dark:text-white tracking-tight">
|
|
172
|
+
{{
|
|
173
|
+
key === "plainFieldsPrompts" ? t("Prompts for non-image fields")
|
|
174
|
+
: key === "generateImages" ? t("Prompts for image fields")
|
|
175
|
+
: t("Prompts for image analysis")
|
|
176
|
+
}}
|
|
177
|
+
</h3>
|
|
178
|
+
<p class="text-xs text-gray-400 mt-0.5">
|
|
179
|
+
{{
|
|
180
|
+
key === "plainFieldsPrompts" ? t("Define how AI should generate content for text-based fields.")
|
|
181
|
+
: key === "generateImages" ? t("Configure prompt rules for generating media and images.")
|
|
182
|
+
: t("Instructions for extracting text specifications from images.")
|
|
183
|
+
}}
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="grid gap-6" :class="Object.keys(promptsCategory).length === 1 ? 'grid-cols-1' : 'grid-cols-1 md:grid-cols-2'">
|
|
189
|
+
<div
|
|
190
|
+
v-for="(prompt, promptKey) in promptsCategory"
|
|
191
|
+
:key="promptKey"
|
|
192
|
+
class="
|
|
193
|
+
flex flex-col bg-white dark:bg-gray-900 border border-gray-200/80 p-5 rounded-default shadow-sm transition-all duration-200
|
|
194
|
+
hover:shadow-md hover:border-gray-300
|
|
195
|
+
dark:border-gray-800
|
|
196
|
+
dark:hover:border-gray-700"
|
|
197
|
+
>
|
|
198
|
+
<div class="flex items-center justify-between mb-4">
|
|
199
|
+
<div class="flex items-center gap-3">
|
|
200
|
+
<div class="p-2 bg-lightPrimary/10 text-lightPrimary rounded-lg shrink-0
|
|
201
|
+
dark:bg-darkPrimary/20
|
|
202
|
+
dark:text-darkPrimary ">
|
|
203
|
+
<IconImageSolid
|
|
204
|
+
v-if="key === 'generateImages'"
|
|
205
|
+
class="w-5 h-5 dark:brightness-200"
|
|
206
|
+
/>
|
|
207
|
+
<IconFileLinesOutline
|
|
208
|
+
v-else
|
|
209
|
+
class="w-5 h-5 dark:brightness-200"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
<div>
|
|
213
|
+
<h4 class="text-sm font-bold text-gray-800 dark:text-gray-200 capitalize tracking-tight">
|
|
214
|
+
{{ formatLabel(promptKey) }}
|
|
215
|
+
</h4>
|
|
216
|
+
<p class="text-[11px] text-gray-400 dark:text-gray-500">
|
|
217
|
+
{{ key === 'generateImages' ? t('Generate source media') : t('Generate a detailed content') }}
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<span class="px-2 py-0.5 text-[10px] font-semibold tracking-wide rounded-md bg-lightPrimary/10 text-lightPrimary dark:bg-darkPrimary/10 dark:text-darkPrimary dark:brightness-200">
|
|
223
|
+
{{ key === 'generateImages' ? t('Image field') : t('Text field') }}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="relative flex-1">
|
|
228
|
+
<Textarea
|
|
229
|
+
v-model="generationPrompts[key][promptKey]"
|
|
230
|
+
class="w-full h-56 p-3.5 text-sm leading-relaxed border border-gray-200 bg-gray-50/30 text-gray-800 shadow-sm resize-none font-sans
|
|
231
|
+
dark:border-gray-700
|
|
232
|
+
dark:bg-gray-800/40
|
|
233
|
+
dark:text-gray-100 rounded-xl
|
|
234
|
+
dark:focus:border-gray-700
|
|
235
|
+
focus:outline-none
|
|
236
|
+
focus:border-gray-200"
|
|
237
|
+
:placeholder="t('Enter prompt instructions...')"
|
|
238
|
+
></Textarea>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
mode="secondary"
|
|
244
|
+
@click="resetPromptToDefault(key, promptKey)"
|
|
245
|
+
class="
|
|
246
|
+
mt-4 flex items-center gap-1.5 text-xs font-semibold bg-transparent px-3 py-1 w-fit rounded-default
|
|
247
|
+
border border-red-200 hover:border-red-600
|
|
248
|
+
text-red-600 hover:text-red-700
|
|
249
|
+
dark:border-red-900/50 dark:hover:border-red-400
|
|
250
|
+
dark:text-red-500 dark:hover:text-red-400
|
|
251
|
+
transition-colors duration-150"
|
|
252
|
+
>
|
|
253
|
+
<IconUndoOutline class="w-3.5 h-3.5" />
|
|
254
|
+
{{ t('Reset') }}
|
|
255
|
+
</button>
|
|
256
|
+
|
|
139
257
|
</div>
|
|
140
258
|
</div>
|
|
141
259
|
</div>
|
|
260
|
+
</template>
|
|
142
261
|
</div>
|
|
143
|
-
<div v-else class="flex flex-col gap-
|
|
144
|
-
<div class="flex
|
|
145
|
-
<div class="flex items-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</
|
|
153
|
-
|
|
262
|
+
<div v-else class="af-confirmation-panel flex flex-col gap-4 w-full h-full">
|
|
263
|
+
<div class="af-overwrite-settings-card flex flex-col p-5 rounded-default border border-gray-100 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900/40">
|
|
264
|
+
<div class="flex items-start justify-between gap-4">
|
|
265
|
+
<div class="flex items-start gap-3 min-w-0">
|
|
266
|
+
<div class="p-2 bg-lightPrimary/10 dark:bg-darkPrimary/10 rounded-xl text-lightPrimary dark:text-darkPrimary shrink-0 mt-0.5">
|
|
267
|
+
<IconShieldCheckOutline class="w-5 h-5 dark:brightness-200" />
|
|
268
|
+
</div>
|
|
269
|
+
<div class="min-w-0">
|
|
270
|
+
<p class="text-base font-semibold text-gray-900 dark:text-white leading-tight">{{ t('Overwrite existing values') }}</p>
|
|
271
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 break-words pr-2">{{ t('AI-generated data will replace existing values.') }}</p>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="shrink-0 pt-1">
|
|
275
|
+
<Toggle v-model="overwriteExistingValues" />
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div v-if="overwriteExistingValues" class="mt-4 flex items-start text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/30 px-3 py-2.5 rounded-xl border border-amber-200 dark:border-amber-900/50 text-sm">
|
|
280
|
+
<IconInfoCircleSolid class="w-4 h-4 me-2 shrink-0 mt-0.5 " />
|
|
281
|
+
<p>{{ t('Warning: Existing values will be overwritten.') }}</p>
|
|
154
282
|
</div>
|
|
155
|
-
<Toggle
|
|
156
|
-
v-model="overwriteExistingValues"
|
|
157
|
-
/>
|
|
158
283
|
</div>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
284
|
+
|
|
285
|
+
<div
|
|
286
|
+
@click="clickSettingsButton()"
|
|
287
|
+
class="af-configure-prompts-card
|
|
288
|
+
flex items-center justify-between p-5 rounded-default border border-gray-100 bg-white shadow-sm cursor-pointer transition-all duration-150
|
|
289
|
+
dark:border-gray-800 dark:bg-gray-900/40 "
|
|
290
|
+
>
|
|
291
|
+
<div class="flex items-start gap-3 min-w-0">
|
|
292
|
+
<div class="p-2 bg-lightPrimary/10 dark:bg-darkPrimary/10 rounded-xl text-lightPrimary dark:text-darkPrimary shrink-0 mt-0.5">
|
|
293
|
+
<IconMessageCaptionOutline class="w-5 h-5 dark:brightness-200" />
|
|
294
|
+
</div>
|
|
295
|
+
<div class="min-w-0">
|
|
296
|
+
<p class="text-base font-semibold text-gray-900 dark:text-white leading-tight">{{ t('Configure prompts')}}</p>
|
|
297
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 break-words">{{ t('Change the instructions for AI to generate actual data.')}}</p>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="flex items-center gap-2 px-1 text-xs font-medium text-gray-400 dark:text-gray-500 dark:brightness-200">
|
|
303
|
+
<IconInfoCircleSolid class="w-4 h-4 " />
|
|
304
|
+
<p>{{ t('This action will only apply to the fields included in your prompts.') }}</p>
|
|
162
305
|
</div>
|
|
163
|
-
|
|
306
|
+
|
|
164
307
|
</div>
|
|
165
308
|
</div>
|
|
166
309
|
</Dialog>
|
|
@@ -176,9 +319,10 @@ import adminforth from '@/adminforth';
|
|
|
176
319
|
import { useI18n } from 'vue-i18n';
|
|
177
320
|
import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
|
|
178
321
|
import { useCoreStore } from '@/stores/core';
|
|
179
|
-
import {
|
|
180
|
-
import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
|
|
322
|
+
import { IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
181
323
|
import { useFiltersStore } from '@/stores/filters';
|
|
324
|
+
import { IconMessageCaptionOutline, IconShieldCheckOutline, IconFileLinesOutline, IconUndoOutline, IconImageSolid, IconRefreshOutline } from '@iconify-prerendered/vue-flowbite';
|
|
325
|
+
import { ProgressBar } from '@/afcl';
|
|
182
326
|
|
|
183
327
|
|
|
184
328
|
const coreStore = useCoreStore();
|
|
@@ -309,16 +453,18 @@ const generationModeButtons = computed(() => {
|
|
|
309
453
|
label: checkedCount.value > 1 ? t('Save fields') : t('Save field'),
|
|
310
454
|
options: {
|
|
311
455
|
disabled: isLoading.value || checkedCount.value < 1 || isFetchingRecords.value || isProcessingAny.value || isGenerationPaused.value,
|
|
312
|
-
loader: isLoading.value,
|
|
456
|
+
loader: isLoading.value,
|
|
457
|
+
class: 'afcl-button w-1/6'
|
|
313
458
|
},
|
|
314
|
-
onclick: async (dialog) => { await saveData(); dialog.hide(); }
|
|
459
|
+
onclick: async (dialog) => { await saveData(); closeDialog(); dialog.hide(false); }
|
|
315
460
|
},
|
|
316
461
|
{
|
|
317
462
|
label: t('Cancel'),
|
|
318
463
|
options: {
|
|
319
|
-
class: '
|
|
464
|
+
class: 'afcl-button w-1/6',
|
|
465
|
+
mode: 'secondary'
|
|
320
466
|
},
|
|
321
|
-
onclick: async (dialog) => { await
|
|
467
|
+
onclick: async (dialog) => { await dialog.hide(true); }
|
|
322
468
|
},
|
|
323
469
|
]
|
|
324
470
|
|
|
@@ -327,7 +473,8 @@ const generationModeButtons = computed(() => {
|
|
|
327
473
|
label: t('Save processed'),
|
|
328
474
|
options: {
|
|
329
475
|
disabled: isLoading.value || isSavingCurrent.value || completedRecordIds.value.size < 1,
|
|
330
|
-
loader: isSavingCurrent.value,
|
|
476
|
+
loader: isSavingCurrent.value,
|
|
477
|
+
class: 'afcl-button w-1/6',
|
|
331
478
|
},
|
|
332
479
|
onclick: async () => { await saveCurrentGenerated(); }
|
|
333
480
|
})
|
|
@@ -335,6 +482,35 @@ const generationModeButtons = computed(() => {
|
|
|
335
482
|
return arrayToReturn;
|
|
336
483
|
});
|
|
337
484
|
|
|
485
|
+
async function regenerateRecord({ recordId }) {
|
|
486
|
+
|
|
487
|
+
const promises = [];
|
|
488
|
+
|
|
489
|
+
if (props.meta.isImageGeneration) {
|
|
490
|
+
promises.push(regenerateImages({ recordId }));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
for (const fieldName of props.meta.outputFieldsForAnalizeFromImages || []) {
|
|
494
|
+
promises.push(
|
|
495
|
+
regenerateCell({
|
|
496
|
+
recordId,
|
|
497
|
+
fieldName,
|
|
498
|
+
})
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (const fieldName of props.meta.outputPlainFields || []) {
|
|
503
|
+
promises.push(
|
|
504
|
+
regenerateCell({
|
|
505
|
+
recordId,
|
|
506
|
+
fieldName,
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
await Promise.allSettled(promises);
|
|
512
|
+
}
|
|
513
|
+
|
|
338
514
|
const handleBeforeClose = async (dialog?: any) => {
|
|
339
515
|
if (popupMode.value === 'generation') {
|
|
340
516
|
const confirmed = await adminforth.confirm({
|
|
@@ -344,16 +520,16 @@ const handleBeforeClose = async (dialog?: any) => {
|
|
|
344
520
|
no: t('Cancel'),
|
|
345
521
|
});
|
|
346
522
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
523
|
+
if (confirmed) {
|
|
524
|
+
closeDialog();
|
|
525
|
+
|
|
526
|
+
if (confirmDialog.value && typeof confirmDialog.value.hide === 'function') {
|
|
527
|
+
confirmDialog.value.hide();
|
|
528
|
+
} else if (dialog && typeof dialog.hide === 'function') {
|
|
529
|
+
dialog.hide();
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
354
532
|
}
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
533
|
return false;
|
|
358
534
|
}
|
|
359
535
|
}
|
|
@@ -384,6 +560,25 @@ const openDialog = async () => {
|
|
|
384
560
|
}
|
|
385
561
|
}
|
|
386
562
|
|
|
563
|
+
const handleBeforeCancel = async () => {
|
|
564
|
+
if (popupMode.value === 'generation') {
|
|
565
|
+
const confirmed = await adminforth.confirm({
|
|
566
|
+
title: t('Close without saving?'),
|
|
567
|
+
message: t('Are you sure you want to close without saving?'),
|
|
568
|
+
yes: t('Yes'),
|
|
569
|
+
no: t('Cancel'),
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
if (confirmed) {
|
|
573
|
+
closeDialog();
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
closeDialog();
|
|
579
|
+
return true;
|
|
580
|
+
};
|
|
581
|
+
|
|
387
582
|
async function getListOfIds() {
|
|
388
583
|
if ( props.meta.recordSelector === 'filtered' ) {
|
|
389
584
|
const filters = filtersStore.getFilters(props.resource.resourceId);
|
|
@@ -490,7 +685,7 @@ function registerGenerationFailure(record: RecordState, actionType: GenerationAc
|
|
|
490
685
|
}
|
|
491
686
|
|
|
492
687
|
function showGenerationFailureSummary() {
|
|
493
|
-
for (const group of generationFailureGroups.values()) {
|
|
688
|
+
for (const group of Array.from(generationFailureGroups.values())) {
|
|
494
689
|
const failedCount = group.recordIds.size;
|
|
495
690
|
const firstRecordId = Array.from(group.recordIds)[0];
|
|
496
691
|
adminforth.alert({
|
|
@@ -754,7 +949,7 @@ async function checkRateLimits() {
|
|
|
754
949
|
return true;
|
|
755
950
|
}
|
|
756
951
|
|
|
757
|
-
async function runActionForRecord(record: RecordState, actionType: GenerationAction) {
|
|
952
|
+
async function runActionForRecord(record: RecordState, actionType: GenerationAction, forceFilterFilledFields?: boolean) {
|
|
758
953
|
if (!checkIfDialogOpen()) {
|
|
759
954
|
return;
|
|
760
955
|
}
|
|
@@ -786,7 +981,7 @@ async function runActionForRecord(record: RecordState, actionType: GenerationAct
|
|
|
786
981
|
actionType,
|
|
787
982
|
recordId: record.id,
|
|
788
983
|
...(customPrompt !== undefined ? { customPrompt: JSON.stringify(customPrompt) } : {}),
|
|
789
|
-
filterFilledFields: !overwriteExistingValues.value,
|
|
984
|
+
filterFilledFields: forceFilterFilledFields !== undefined ? forceFilterFilledFields: !overwriteExistingValues.value,
|
|
790
985
|
},
|
|
791
986
|
silentError: true,
|
|
792
987
|
});
|
|
@@ -874,17 +1069,17 @@ function applyJobResult(record: RecordState, job: any, actionType: GenerationAct
|
|
|
874
1069
|
}
|
|
875
1070
|
|
|
876
1071
|
function applyJobFailure(record: RecordState, job: any, actionType: GenerationAction) {
|
|
877
|
-
registerGenerationFailure(record, actionType, job?.error || 'Unknown error');
|
|
1072
|
+
registerGenerationFailure(record, actionType, job?.error || t('Unknown error'));
|
|
878
1073
|
if (actionType === 'generate_images') {
|
|
879
1074
|
record.imageGenerationFailed = true;
|
|
880
|
-
record.imageGenerationErrorMessage = job?.error || 'Unknown error';
|
|
1075
|
+
record.imageGenerationErrorMessage = job?.error || t('Unknown error');
|
|
881
1076
|
} else if (actionType === 'analyze') {
|
|
882
1077
|
for (const field of Object.keys(props.meta.outputFieldsForAnalizeFromImages || {})) {
|
|
883
|
-
record.imageToTextErrorMessages[props.meta.outputFieldsForAnalizeFromImages[field]] = job?.error || 'Unknown error';
|
|
1078
|
+
record.imageToTextErrorMessages[props.meta.outputFieldsForAnalizeFromImages[field]] = job?.error || t('Unknown error');
|
|
884
1079
|
}
|
|
885
1080
|
} else if (actionType === 'analyze_no_images') {
|
|
886
1081
|
for (const field of Object.keys(props.meta.outputPlainFields || {})) {
|
|
887
|
-
record.textToTextErrorMessages[props.meta.outputPlainFields[field]] = job?.error || 'Unknown error';
|
|
1082
|
+
record.textToTextErrorMessages[props.meta.outputPlainFields[field]] = job?.error || t('Unknown error');
|
|
888
1083
|
}
|
|
889
1084
|
}
|
|
890
1085
|
touchRecords();
|
|
@@ -1232,17 +1427,17 @@ async function uploadImage(imgBlob, id, fieldName) {
|
|
|
1232
1427
|
} catch (error) {
|
|
1233
1428
|
console.error('Error uploading file:', error);
|
|
1234
1429
|
adminforth.alert({
|
|
1235
|
-
message: 'Sorry but the file was not be uploaded. Please try again.',
|
|
1430
|
+
message: t('Sorry but the file was not be uploaded. Please try again.'),
|
|
1236
1431
|
variant: 'danger'
|
|
1237
1432
|
});
|
|
1238
1433
|
|
|
1239
1434
|
isError.value = true;
|
|
1240
|
-
errorMessage.value =
|
|
1435
|
+
errorMessage.value = t('Failed to upload images. Please, try to re-run the action.');
|
|
1241
1436
|
return null;
|
|
1242
1437
|
}
|
|
1243
1438
|
}
|
|
1244
1439
|
|
|
1245
|
-
function regenerateImages({ recordId }: { recordId: any }) {
|
|
1440
|
+
async function regenerateImages({ recordId }: { recordId: any }) {
|
|
1246
1441
|
if (coreStore.isInternetError) {
|
|
1247
1442
|
adminforth.alert({
|
|
1248
1443
|
message: t('Cannot regenerate images while internet connection is lost. Please check your connection and try again.'),
|
|
@@ -1256,11 +1451,27 @@ function regenerateImages({ recordId }: { recordId: any }) {
|
|
|
1256
1451
|
return;
|
|
1257
1452
|
}
|
|
1258
1453
|
record.aiStatus.generatedImages = false;
|
|
1454
|
+
record.status = 'processing';
|
|
1259
1455
|
touchRecords();
|
|
1260
|
-
|
|
1456
|
+
|
|
1457
|
+
try {
|
|
1458
|
+
await runActionForRecord(
|
|
1459
|
+
record,
|
|
1460
|
+
'generate_images',
|
|
1461
|
+
false
|
|
1462
|
+
);
|
|
1463
|
+
|
|
1464
|
+
record.status = 'completed';
|
|
1465
|
+
completedRecordIds.value.add(String(recordId));
|
|
1466
|
+
|
|
1261
1467
|
record.aiStatus.generatedImages = true;
|
|
1262
1468
|
touchRecords();
|
|
1263
|
-
|
|
1469
|
+
|
|
1470
|
+
} catch (e) {
|
|
1471
|
+
record.aiStatus.generatedImages = true;
|
|
1472
|
+
record.status = 'failed';
|
|
1473
|
+
touchRecords();
|
|
1474
|
+
}
|
|
1264
1475
|
}
|
|
1265
1476
|
|
|
1266
1477
|
async function findPreviewURLForImages() {
|
|
@@ -1326,10 +1537,14 @@ async function getGenerationPrompts() {
|
|
|
1326
1537
|
const calculatedGenerationPrompts: any = {};
|
|
1327
1538
|
const savedPrompts = localStorage.getItem(`bulkAiFlowGenerationPrompts_${props.meta.pluginInstanceId}`);
|
|
1328
1539
|
if (props.meta.generationPrompts.plainFieldsPrompts) {
|
|
1329
|
-
calculatedGenerationPrompts.plainFieldsPrompts =
|
|
1540
|
+
calculatedGenerationPrompts.plainFieldsPrompts = {
|
|
1541
|
+
...props.meta.generationPrompts.plainFieldsPrompts
|
|
1542
|
+
};
|
|
1330
1543
|
}
|
|
1331
1544
|
if (props.meta.generationPrompts.imageFieldsPrompts) {
|
|
1332
|
-
calculatedGenerationPrompts.imageFieldsPrompts =
|
|
1545
|
+
calculatedGenerationPrompts.imageFieldsPrompts = {
|
|
1546
|
+
...props.meta.generationPrompts.imageFieldsPrompts
|
|
1547
|
+
};
|
|
1333
1548
|
}
|
|
1334
1549
|
if (props.meta.generationPrompts.imageGenerationPrompts) {
|
|
1335
1550
|
let imageFields = {};
|
|
@@ -1596,4 +1811,16 @@ async function saveCurrentGenerated() {
|
|
|
1596
1811
|
props.updateList();
|
|
1597
1812
|
}
|
|
1598
1813
|
|
|
1814
|
+
const generationStatusClass = computed(() => {
|
|
1815
|
+
if (isGenerationPaused.value) {
|
|
1816
|
+
return 'bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-950/30 dark:text-yellow-400 dark:border-yellow-400'
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if (displayedProcessedCount.value === totalRecords.value) {
|
|
1820
|
+
return 'bg-green-100 text-green-800 border-green-200 dark:bg-green-950/30 dark:text-green-400 dark:border-green-400'
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
return 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950/30 dark:text-blue-400 dark:border-blue-400'
|
|
1824
|
+
})
|
|
1825
|
+
|
|
1599
1826
|
</script>
|