@adminforth/bulk-ai-flow 1.7.4 → 1.8.1
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/README.md +7 -1
- package/build.log +5 -5
- package/custom/{imageGenerationCarousel.vue → ImageGenerationCarousel.vue} +18 -14
- package/custom/{visionAction.vue → VisionAction.vue} +57 -75
- package/{dist/custom/visionTable.vue → custom/VisionTable.vue} +41 -27
- package/dist/custom/{imageGenerationCarousel.vue → ImageGenerationCarousel.vue} +18 -14
- package/dist/custom/{visionAction.vue → VisionAction.vue} +57 -75
- package/{custom/visionTable.vue → dist/custom/VisionTable.vue} +41 -27
- package/dist/index.js +99 -60
- package/index.ts +111 -86
- package/package.json +1 -1
- package/types.ts +37 -1
package/README.md
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
# AdminForth Bulk AI Flow Plugin
|
|
2
|
+
|
|
3
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /> <img src="https://woodpecker.devforth.io/api/badges/3848/status.svg" alt="Build Status" /> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow"> <img src="https://img.shields.io/npm/dt/@adminforth/bulk-ai-flow" alt="npm downloads" /></a> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow"><img src="https://img.shields.io/npm/v/@adminforth/bulk-ai-flow" alt="npm version" /></a> <a href="https://www.npmjs.com/package/@adminforth/bulk-ai-flow">
|
|
4
|
+
|
|
5
|
+
Allows you to log changes made to table records via adminforth.
|
|
6
|
+
|
|
7
|
+
## For usage, see [AdminForth Bulk AI Flow Documentation](https://adminforth.dev/docs/tutorial/Plugins/bulk-ai-flow/)
|
package/build.log
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
sending incremental file list
|
|
6
6
|
custom/
|
|
7
|
-
custom/
|
|
7
|
+
custom/ImageGenerationCarousel.vue
|
|
8
|
+
custom/VisionAction.vue
|
|
9
|
+
custom/VisionTable.vue
|
|
8
10
|
custom/package-lock.json
|
|
9
11
|
custom/package.json
|
|
10
12
|
custom/tsconfig.json
|
|
11
|
-
custom/visionAction.vue
|
|
12
|
-
custom/visionTable.vue
|
|
13
13
|
|
|
14
|
-
sent
|
|
15
|
-
total size is
|
|
14
|
+
sent 179,998 bytes received 134 bytes 360,264.00 bytes/sec
|
|
15
|
+
total size is 179,465 speedup is 1.00
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
3
|
<!-- Main modal -->
|
|
4
|
-
<div tabindex="-1" class="
|
|
5
|
-
<div class="relative p-4 w-
|
|
4
|
+
<div tabindex="-1" class="fixed inset-0 z-10 flex justify-center items-center dark:bg-gray-900/50 overflow-y-auto">
|
|
5
|
+
<div class="relative p-4 w-full max-w-[1600px] max-h-[90vh] ">
|
|
6
6
|
<!-- Modal content -->
|
|
7
7
|
<div class="relative bg-white rounded-lg shadow-xl dark:bg-gray-700">
|
|
8
8
|
<!-- Modal header -->
|
|
@@ -95,20 +95,20 @@
|
|
|
95
95
|
</div>
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
<div id="gallery" class="relative w-full" data-carousel="static">
|
|
98
|
+
<div id="gallery" class="relative w-full min-w-0" data-carousel="static">
|
|
99
99
|
<!-- Carousel wrapper -->
|
|
100
100
|
<div class="relative h-56 overflow-hidden rounded-lg md:h-[calc(100vh-400px)]">
|
|
101
101
|
<!-- Item 1 -->
|
|
102
102
|
<div
|
|
103
103
|
v-for="(img, index) in images"
|
|
104
104
|
:key="index"
|
|
105
|
+
class="flex items-center justify-center w-full h-full"
|
|
105
106
|
:class="[
|
|
106
|
-
index === 0 ? 'block' : 'hidden'
|
|
107
|
-
'duration-700 ease-in-out'
|
|
107
|
+
index === 0 ? 'block' : 'hidden'
|
|
108
108
|
]"
|
|
109
109
|
data-carousel-item
|
|
110
110
|
>
|
|
111
|
-
<img :src="img" class="
|
|
111
|
+
<img :src="img" class="max-w-full max-h-full object-contain"
|
|
112
112
|
:alt="`Generated image ${index + 1}`"
|
|
113
113
|
/>
|
|
114
114
|
</div>
|
|
@@ -175,10 +175,6 @@
|
|
|
175
175
|
</div>
|
|
176
176
|
</div>
|
|
177
177
|
</div>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
178
|
</template>
|
|
183
179
|
|
|
184
180
|
<script setup lang="ts">
|
|
@@ -193,8 +189,8 @@ import { ProgressBar } from '@/afcl';
|
|
|
193
189
|
const { t: $t } = useI18n();
|
|
194
190
|
|
|
195
191
|
const prompt = ref('');
|
|
196
|
-
const emit = defineEmits(['close', 'selectImage', 'error']);
|
|
197
|
-
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage']);
|
|
192
|
+
const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
|
|
193
|
+
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex']);
|
|
198
194
|
const images = ref([]);
|
|
199
195
|
const loading = ref(false);
|
|
200
196
|
const attachmentFiles = ref<string[]>([])
|
|
@@ -208,12 +204,14 @@ function minifyField(field: string): string {
|
|
|
208
204
|
|
|
209
205
|
const caurosel = ref(null);
|
|
210
206
|
onMounted(async () => {
|
|
211
|
-
|
|
207
|
+
for (const img of props.images || []) {
|
|
208
|
+
images.value.push(img);
|
|
209
|
+
}
|
|
212
210
|
const temp = await getGenerationPrompt() || '';
|
|
213
211
|
prompt.value = temp[props.fieldName];
|
|
214
212
|
await nextTick();
|
|
215
213
|
|
|
216
|
-
const currentIndex =
|
|
214
|
+
const currentIndex = props.carouselImageIndex || 0;
|
|
217
215
|
caurosel.value = new Carousel(
|
|
218
216
|
document.getElementById('gallery'),
|
|
219
217
|
images.value.map((img, index) => {
|
|
@@ -298,6 +296,12 @@ async function confirmImage() {
|
|
|
298
296
|
const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
|
|
299
297
|
const img = images.value[currentIndex];
|
|
300
298
|
|
|
299
|
+
props.images.splice(0, props.images.length);
|
|
300
|
+
for (const i of images.value) {
|
|
301
|
+
props.images.push(i);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
emit('updateCarouselIndex', currentIndex, props.recordId, props.fieldName);
|
|
301
305
|
emit('selectImage', img, props.recordId, props.fieldName);
|
|
302
306
|
emit('close');
|
|
303
307
|
|
|
@@ -5,60 +5,38 @@
|
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@error="handleTableError"
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
43
|
-
<div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
|
|
44
|
-
<div class="h-full text-red-600 font-semibold flex items-center justify-center md:mb-2">
|
|
45
|
-
<p v-if="isError === true">{{ errorMessage }}</p>
|
|
46
|
-
</div>
|
|
47
|
-
<button type="button" class="w-full md:w-auto py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
48
|
-
@click="closeDialog"
|
|
49
|
-
>
|
|
50
|
-
{{'Cancel'}}
|
|
51
|
-
</button>
|
|
52
|
-
<Button
|
|
53
|
-
class="w-full md:w-64"
|
|
54
|
-
@click="saveData"
|
|
55
|
-
:disabled="isLoading || checkedCount < 1 || isCriticalError"
|
|
56
|
-
:loader="isLoading"
|
|
57
|
-
>
|
|
58
|
-
{{ checkedCount > 1 ? 'Save fields' : 'Save field' }}
|
|
59
|
-
</Button>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
8
|
+
<Dialog
|
|
9
|
+
ref="confirmDialog"
|
|
10
|
+
header="Bulk AI Flow"
|
|
11
|
+
class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
|
|
12
|
+
:buttons="[
|
|
13
|
+
{ label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError || isFetchingRecords, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } },
|
|
14
|
+
{ label: 'Cancel', onclick: (dialog) => dialog.hide() },
|
|
15
|
+
]"
|
|
16
|
+
>
|
|
17
|
+
<div class="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">
|
|
18
|
+
<div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
|
|
19
|
+
<VisionTable
|
|
20
|
+
:checkbox="props.checkboxes"
|
|
21
|
+
:records="records"
|
|
22
|
+
:meta="props.meta"
|
|
23
|
+
:images="images"
|
|
24
|
+
:tableHeaders="tableHeaders"
|
|
25
|
+
:tableColumns="tableColumns"
|
|
26
|
+
:customFieldNames="customFieldNames"
|
|
27
|
+
:tableColumnsIndexes="tableColumnsIndexes"
|
|
28
|
+
:selected="selected"
|
|
29
|
+
:isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
|
|
30
|
+
:isAiResponseReceivedImage="isAiResponseReceivedImage"
|
|
31
|
+
:primaryKey="primaryKey"
|
|
32
|
+
:openGenerationCarousel="openGenerationCarousel"
|
|
33
|
+
@error="handleTableError"
|
|
34
|
+
:carouselSaveImages="carouselSaveImages"
|
|
35
|
+
:carouselImageIndex="carouselImageIndex"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="text-red-600 flex items-center w-full">
|
|
39
|
+
<p v-if="isError === true">{{ errorMessage }}</p>
|
|
62
40
|
</div>
|
|
63
41
|
</div>
|
|
64
42
|
</Dialog>
|
|
@@ -68,13 +46,11 @@
|
|
|
68
46
|
import { callAdminForthApi } from '@/utils';
|
|
69
47
|
import { Ref, ref, watch } from 'vue'
|
|
70
48
|
import { Dialog, Button } from '@/afcl';
|
|
71
|
-
import VisionTable from './
|
|
49
|
+
import VisionTable from './VisionTable.vue'
|
|
72
50
|
import adminforth from '@/adminforth';
|
|
73
51
|
import { useI18n } from 'vue-i18n';
|
|
74
|
-
import { useRoute } from 'vue-router';
|
|
75
52
|
import { AdminUser, type AdminForthResourceCommon } from '@/types';
|
|
76
53
|
|
|
77
|
-
const route = useRoute();
|
|
78
54
|
const { t } = useI18n();
|
|
79
55
|
|
|
80
56
|
const props = defineProps<{
|
|
@@ -99,11 +75,14 @@ const tableColumns = ref([]);
|
|
|
99
75
|
const tableColumnsIndexes = ref([]);
|
|
100
76
|
const customFieldNames = ref([]);
|
|
101
77
|
const selected = ref<any[]>([]);
|
|
78
|
+
const carouselSaveImages = ref<any[]>([]);
|
|
79
|
+
const carouselImageIndex = ref<any[]>([]);
|
|
102
80
|
const isAiResponseReceivedAnalize = ref([]);
|
|
103
81
|
const isAiResponseReceivedImage = ref([]);
|
|
104
82
|
const primaryKey = props.meta.primaryKey;
|
|
105
83
|
const openGenerationCarousel = ref([]);
|
|
106
84
|
const isLoading = ref(false);
|
|
85
|
+
const isFetchingRecords = ref(false);
|
|
107
86
|
const isError = ref(false);
|
|
108
87
|
const isCriticalError = ref(false);
|
|
109
88
|
const isImageGenerationError = ref(false);
|
|
@@ -128,7 +107,7 @@ const openDialog = async () => {
|
|
|
128
107
|
return acc;
|
|
129
108
|
},{[primaryKey]: records.value[i][primaryKey]} as Record<string, boolean>);
|
|
130
109
|
}
|
|
131
|
-
|
|
110
|
+
isFetchingRecords.value = true;
|
|
132
111
|
const tasks = [];
|
|
133
112
|
if (props.meta.isFieldsForAnalizeFromImages) {
|
|
134
113
|
tasks.push(runAiAction({
|
|
@@ -152,7 +131,12 @@ const openDialog = async () => {
|
|
|
152
131
|
}));
|
|
153
132
|
}
|
|
154
133
|
await Promise.all(tasks);
|
|
155
|
-
|
|
134
|
+
|
|
135
|
+
if (props.meta.isImageGeneration) {
|
|
136
|
+
fillCarouselSaveImages();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
isFetchingRecords.value = false;
|
|
156
140
|
}
|
|
157
141
|
|
|
158
142
|
watch(selected, (val) => {
|
|
@@ -160,22 +144,22 @@ watch(selected, (val) => {
|
|
|
160
144
|
checkedCount.value = val.filter(item => item.isChecked === true).length;
|
|
161
145
|
}, { deep: true });
|
|
162
146
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
errorMessage.value = '';
|
|
147
|
+
function fillCarouselSaveImages() {
|
|
148
|
+
for (const item of selected.value) {
|
|
149
|
+
const tempItem: any = {};
|
|
150
|
+
const tempItemIndex: any = {};
|
|
151
|
+
for (const [key, value] of Object.entries(item)) {
|
|
152
|
+
if (props.meta.outputImageFields?.includes(key)) {
|
|
153
|
+
tempItem[key] = [value];
|
|
154
|
+
tempItemIndex[key] = 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
carouselSaveImages.value.push(tempItem);
|
|
158
|
+
carouselImageIndex.value.push(tempItemIndex);
|
|
159
|
+
}
|
|
177
160
|
}
|
|
178
161
|
|
|
162
|
+
|
|
179
163
|
function formatLabel(str) {
|
|
180
164
|
return str
|
|
181
165
|
.split('_')
|
|
@@ -270,7 +254,6 @@ async function getRecords() {
|
|
|
270
254
|
console.error('Failed to get records:', error);
|
|
271
255
|
isError.value = true;
|
|
272
256
|
errorMessage.value = `Failed to fetch records. Please, try to re-run the action.`;
|
|
273
|
-
// Handle error appropriately
|
|
274
257
|
}
|
|
275
258
|
}
|
|
276
259
|
|
|
@@ -288,7 +271,6 @@ async function getImages() {
|
|
|
288
271
|
console.error('Failed to get images:', error);
|
|
289
272
|
isError.value = true;
|
|
290
273
|
errorMessage.value = `Failed to fetch images. Please, try to re-run the action.`;
|
|
291
|
-
// Handle error appropriately
|
|
292
274
|
}
|
|
293
275
|
}
|
|
294
276
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
3
2
|
<Table
|
|
4
3
|
:columns="tableHeaders"
|
|
5
4
|
:data="tableColumns"
|
|
6
|
-
:pageSize="
|
|
5
|
+
:pageSize="6"
|
|
7
6
|
>
|
|
8
7
|
<!-- HEADER TEMPLATE -->
|
|
9
8
|
<template #header:checkboxes="{ item }">
|
|
@@ -28,18 +27,22 @@
|
|
|
28
27
|
@click="zoomImage(image)"
|
|
29
28
|
/>
|
|
30
29
|
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<transition name="fade">
|
|
31
32
|
<div
|
|
32
33
|
v-if="zoomedImage"
|
|
33
34
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
34
35
|
@click.self="closeZoom"
|
|
35
36
|
>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
<transition name="zoom">
|
|
38
|
+
<img
|
|
39
|
+
v-if="zoomedImage"
|
|
40
|
+
:src="zoomedImage"
|
|
41
|
+
class="max-w-full max-h-full rounded-lg object-contain cursor-grab z-75"
|
|
42
|
+
/>
|
|
43
|
+
</transition>
|
|
41
44
|
</div>
|
|
42
|
-
</
|
|
45
|
+
</transition>
|
|
43
46
|
</div>
|
|
44
47
|
</template>
|
|
45
48
|
<!-- CUSTOM FIELD TEMPLATES -->
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
<Input
|
|
74
77
|
type="number"
|
|
75
78
|
v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
|
|
76
|
-
class="w-full "
|
|
79
|
+
class="w-full min-w-[80px]"
|
|
77
80
|
:fullWidth="true"
|
|
78
81
|
/>
|
|
79
82
|
</div>
|
|
@@ -91,13 +94,15 @@
|
|
|
91
94
|
<div>
|
|
92
95
|
<GenerationCarousel
|
|
93
96
|
v-if="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
|
|
94
|
-
:images="
|
|
97
|
+
:images="carouselSaveImages[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
|
|
95
98
|
:recordId="item[primaryKey]"
|
|
96
99
|
:meta="props.meta"
|
|
97
100
|
:fieldName="n"
|
|
101
|
+
:carouselImageIndex="carouselImageIndex[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n]"
|
|
98
102
|
@error="handleError"
|
|
99
103
|
@close="openGenerationCarousel[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])][n] = false"
|
|
100
104
|
@selectImage="updateSelectedImage"
|
|
105
|
+
@updateCarouselIndex="updateActiveIndex"
|
|
101
106
|
/>
|
|
102
107
|
</div>
|
|
103
108
|
</div>
|
|
@@ -112,14 +117,12 @@
|
|
|
112
117
|
</div>
|
|
113
118
|
</template>
|
|
114
119
|
</Table>
|
|
115
|
-
</div>
|
|
116
120
|
</template>
|
|
117
121
|
|
|
118
122
|
<script lang="ts" setup>
|
|
119
|
-
import { ref
|
|
120
|
-
import mediumZoom from 'medium-zoom'
|
|
123
|
+
import { ref } from 'vue'
|
|
121
124
|
import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle } from '@/afcl'
|
|
122
|
-
import GenerationCarousel from './
|
|
125
|
+
import GenerationCarousel from './ImageGenerationCarousel.vue'
|
|
123
126
|
|
|
124
127
|
const props = defineProps<{
|
|
125
128
|
meta: any,
|
|
@@ -134,12 +137,13 @@ const props = defineProps<{
|
|
|
134
137
|
openGenerationCarousel: any
|
|
135
138
|
isError: boolean,
|
|
136
139
|
errorMessage: string
|
|
140
|
+
carouselSaveImages: any[]
|
|
141
|
+
carouselImageIndex: any[]
|
|
137
142
|
}>();
|
|
138
143
|
const emit = defineEmits(['error']);
|
|
139
144
|
|
|
140
145
|
|
|
141
146
|
const zoomedImage = ref(null)
|
|
142
|
-
const zoomedImg = ref(null)
|
|
143
147
|
|
|
144
148
|
|
|
145
149
|
function zoomImage(img) {
|
|
@@ -150,17 +154,6 @@ function closeZoom() {
|
|
|
150
154
|
zoomedImage.value = null
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
watch(zoomedImage, async (val) => {
|
|
154
|
-
await nextTick()
|
|
155
|
-
if (val && zoomedImg.value) {
|
|
156
|
-
mediumZoom(zoomedImg.value, {
|
|
157
|
-
margin: 24,
|
|
158
|
-
background: 'rgba(0, 0, 0, 0.9)',
|
|
159
|
-
scrollOffset: 150
|
|
160
|
-
}).show()
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
157
|
function isInColumnEnum(key: string): boolean {
|
|
165
158
|
const colEnum = props.meta.columnEnums?.find(c => c.name === key);
|
|
166
159
|
if (!colEnum) {
|
|
@@ -193,4 +186,25 @@ function handleError({ isError, errorMessage }) {
|
|
|
193
186
|
});
|
|
194
187
|
}
|
|
195
188
|
|
|
196
|
-
|
|
189
|
+
function updateActiveIndex(newIndex: number, id: any, fieldName: string) {
|
|
190
|
+
props.carouselImageIndex[props.tableColumnsIndexes.findIndex(el => el[props.primaryKey] === id)][fieldName] = newIndex;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<style scoped>
|
|
196
|
+
.fade-enter-active, .fade-leave-active {
|
|
197
|
+
transition: opacity 0.2s ease;
|
|
198
|
+
}
|
|
199
|
+
.fade-enter-from, .fade-leave-to {
|
|
200
|
+
opacity: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.zoom-enter-active, .zoom-leave-active {
|
|
204
|
+
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
205
|
+
}
|
|
206
|
+
.zoom-enter-from, .zoom-leave-to {
|
|
207
|
+
transform: scale(0.95);
|
|
208
|
+
opacity: 0;
|
|
209
|
+
}
|
|
210
|
+
</style>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
3
|
<!-- Main modal -->
|
|
4
|
-
<div tabindex="-1" class="
|
|
5
|
-
<div class="relative p-4 w-
|
|
4
|
+
<div tabindex="-1" class="fixed inset-0 z-10 flex justify-center items-center dark:bg-gray-900/50 overflow-y-auto">
|
|
5
|
+
<div class="relative p-4 w-full max-w-[1600px] max-h-[90vh] ">
|
|
6
6
|
<!-- Modal content -->
|
|
7
7
|
<div class="relative bg-white rounded-lg shadow-xl dark:bg-gray-700">
|
|
8
8
|
<!-- Modal header -->
|
|
@@ -95,20 +95,20 @@
|
|
|
95
95
|
</div>
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
<div id="gallery" class="relative w-full" data-carousel="static">
|
|
98
|
+
<div id="gallery" class="relative w-full min-w-0" data-carousel="static">
|
|
99
99
|
<!-- Carousel wrapper -->
|
|
100
100
|
<div class="relative h-56 overflow-hidden rounded-lg md:h-[calc(100vh-400px)]">
|
|
101
101
|
<!-- Item 1 -->
|
|
102
102
|
<div
|
|
103
103
|
v-for="(img, index) in images"
|
|
104
104
|
:key="index"
|
|
105
|
+
class="flex items-center justify-center w-full h-full"
|
|
105
106
|
:class="[
|
|
106
|
-
index === 0 ? 'block' : 'hidden'
|
|
107
|
-
'duration-700 ease-in-out'
|
|
107
|
+
index === 0 ? 'block' : 'hidden'
|
|
108
108
|
]"
|
|
109
109
|
data-carousel-item
|
|
110
110
|
>
|
|
111
|
-
<img :src="img" class="
|
|
111
|
+
<img :src="img" class="max-w-full max-h-full object-contain"
|
|
112
112
|
:alt="`Generated image ${index + 1}`"
|
|
113
113
|
/>
|
|
114
114
|
</div>
|
|
@@ -175,10 +175,6 @@
|
|
|
175
175
|
</div>
|
|
176
176
|
</div>
|
|
177
177
|
</div>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
178
|
</template>
|
|
183
179
|
|
|
184
180
|
<script setup lang="ts">
|
|
@@ -193,8 +189,8 @@ import { ProgressBar } from '@/afcl';
|
|
|
193
189
|
const { t: $t } = useI18n();
|
|
194
190
|
|
|
195
191
|
const prompt = ref('');
|
|
196
|
-
const emit = defineEmits(['close', 'selectImage', 'error']);
|
|
197
|
-
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage']);
|
|
192
|
+
const emit = defineEmits(['close', 'selectImage', 'error', 'updateCarouselIndex']);
|
|
193
|
+
const props = defineProps(['meta', 'record', 'images', 'recordId', 'prompt', 'fieldName', 'isError', 'errorMessage', 'carouselImageIndex']);
|
|
198
194
|
const images = ref([]);
|
|
199
195
|
const loading = ref(false);
|
|
200
196
|
const attachmentFiles = ref<string[]>([])
|
|
@@ -208,12 +204,14 @@ function minifyField(field: string): string {
|
|
|
208
204
|
|
|
209
205
|
const caurosel = ref(null);
|
|
210
206
|
onMounted(async () => {
|
|
211
|
-
|
|
207
|
+
for (const img of props.images || []) {
|
|
208
|
+
images.value.push(img);
|
|
209
|
+
}
|
|
212
210
|
const temp = await getGenerationPrompt() || '';
|
|
213
211
|
prompt.value = temp[props.fieldName];
|
|
214
212
|
await nextTick();
|
|
215
213
|
|
|
216
|
-
const currentIndex =
|
|
214
|
+
const currentIndex = props.carouselImageIndex || 0;
|
|
217
215
|
caurosel.value = new Carousel(
|
|
218
216
|
document.getElementById('gallery'),
|
|
219
217
|
images.value.map((img, index) => {
|
|
@@ -298,6 +296,12 @@ async function confirmImage() {
|
|
|
298
296
|
const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
|
|
299
297
|
const img = images.value[currentIndex];
|
|
300
298
|
|
|
299
|
+
props.images.splice(0, props.images.length);
|
|
300
|
+
for (const i of images.value) {
|
|
301
|
+
props.images.push(i);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
emit('updateCarouselIndex', currentIndex, props.recordId, props.fieldName);
|
|
301
305
|
emit('selectImage', img, props.recordId, props.fieldName);
|
|
302
306
|
emit('close');
|
|
303
307
|
|