@adminforth/upload 2.15.15 → 2.15.16
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/imageGenerator.vue +191 -195
- package/dist/custom/imageGenerator.vue +191 -195
- package/package.json +1 -1
package/build.log
CHANGED
|
@@ -12,5 +12,5 @@ custom/preview.vue
|
|
|
12
12
|
custom/tsconfig.json
|
|
13
13
|
custom/uploader.vue
|
|
14
14
|
|
|
15
|
-
sent
|
|
16
|
-
total size is
|
|
15
|
+
sent 66,735 bytes received 153 bytes 133,776.00 bytes/sec
|
|
16
|
+
total size is 66,181 speedup is 0.99
|
|
@@ -1,217 +1,215 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
3
|
<!-- Main modal -->
|
|
4
|
-
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden fixed
|
|
5
|
-
<div class="relative p-4 w-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@click="zoomImage(img)"
|
|
44
|
-
/>
|
|
45
|
-
<div
|
|
46
|
-
class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
|
|
47
|
-
@click="removeFileFromList(key)"
|
|
48
|
-
>
|
|
49
|
-
<Tooltip class="absolute top-0 end-0">
|
|
50
|
-
<div>
|
|
51
|
-
<div class="w-4 h-4 absolute"></div>
|
|
52
|
-
<IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
|
|
53
|
-
</div>
|
|
54
|
-
<template #tooltip>
|
|
55
|
-
Remove file
|
|
56
|
-
</template>
|
|
57
|
-
</Tooltip>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<input
|
|
61
|
-
ref="fileInput"
|
|
62
|
-
class="hidden"
|
|
63
|
-
type="file"
|
|
64
|
-
@change="handleAddFile"
|
|
65
|
-
accept="image/*"
|
|
66
|
-
/>
|
|
67
|
-
<button v-if="!uploading" @click="fileInput?.click()" type="button" class="relative group hover:border-gray-500 transition border-gray-300 flex items-center justify-center w-20 h-20 border-2 border-dashed rounded-md">
|
|
68
|
-
<div class="flex flex-col items-center justify-center gap-2 mt-4 mb-4">
|
|
69
|
-
<IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-6 h-6 text-gray-300 hover:text-gray-300" />
|
|
70
|
-
<p class="text-gray-300 group-hover:text-gray-500 transition bottom-0">Ctrl + v</p>
|
|
71
|
-
</div>
|
|
72
|
-
</button>
|
|
73
|
-
<Skeleton v-else type="image" class="w-20 h-20" />
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<!-- Fullscreen Modal -->
|
|
4
|
+
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden fixed inset-0 z-50 flex justify-center items-center w-full h-full bg-black/50">
|
|
5
|
+
<div class="relative p-4 w-11/12 max-w-5xl max-h-full ">
|
|
6
|
+
<!-- Modal content -->
|
|
7
|
+
<div class="relative bg-white rounded-xl shadow-2xl dark:bg-gray-800">
|
|
8
|
+
<!-- Header -->
|
|
9
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
10
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
11
|
+
{{ $t('Generate image with AI') }}
|
|
12
|
+
</h3>
|
|
13
|
+
<button type="button"
|
|
14
|
+
@click="() => {stopGeneration = true; emit('close') }"
|
|
15
|
+
class="text-gray-400 bg-transparent hover:bg-gray-100 hover:text-gray-900 rounded-lg w-8 h-8 inline-flex justify-center items-center dark:hover:bg-gray-700 dark:hover:text-white transition" >
|
|
16
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
17
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
18
|
+
</svg>
|
|
19
|
+
<span class="sr-only">{{ $t('Close modal') }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
<!-- Modal body -->
|
|
23
|
+
<div class="px-6 py-5 space-y-4">
|
|
24
|
+
<!-- PROMPT TEXTAREA -->
|
|
25
|
+
<!-- Textarea -->
|
|
26
|
+
<textarea
|
|
27
|
+
rows="3"
|
|
28
|
+
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-200 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 resize-none"
|
|
29
|
+
:placeholder="$t('Prompt which will be passed to AI network')"
|
|
30
|
+
v-model="prompt"
|
|
31
|
+
:title="$t('Prompt which will be passed to AI network')"
|
|
32
|
+
></textarea>
|
|
33
|
+
|
|
34
|
+
<!-- Thumbnails -->
|
|
35
|
+
<div class="flex flex-wrap gap-2">
|
|
36
|
+
<div class="group relative" v-for="(img, key) in requestAttachmentFilesUrls" :key="key">
|
|
37
|
+
<img
|
|
38
|
+
:src="img"
|
|
39
|
+
class="w-14 h-14 object-cover rounded-lg cursor-pointer border border-gray-200 hover:border-blue-500 transition"
|
|
40
|
+
:alt="`Attachment ${key + 1}`"
|
|
41
|
+
@click="zoomImage(img)"
|
|
42
|
+
/>
|
|
77
43
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@click.self="closeZoom"
|
|
44
|
+
class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
|
|
45
|
+
@click="removeFileFromList(key)"
|
|
81
46
|
>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
47
|
+
<Tooltip class="absolute top-0 end-0">
|
|
48
|
+
<div>
|
|
49
|
+
<div class="w-4 h-4 absolute"></div>
|
|
50
|
+
<IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
|
|
51
|
+
</div>
|
|
52
|
+
<template #tooltip>Remove file</template>
|
|
53
|
+
</Tooltip>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<input ref="fileInput" class="hidden" type="file" @change="handleAddFile" accept="image/*" />
|
|
57
|
+
<button v-if="!uploading" @click="fileInput?.click()" type="button" :disabled="requestAttachmentFilesUrls.length >= 1" class=" upload-btn relative group hover:border-gray-400 disabled:opacity-40 disabled:cursor-not-allowed transition border-gray-300 flex items-center justify-center w-14 h-14 border-2 border-dashed rounded-lg">
|
|
58
|
+
<div class="flex flex-col items-center justify-center gap-1">
|
|
59
|
+
<IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-5 h-5 text-gray-300" />
|
|
60
|
+
<p class="text-gray-300 group-hover:text-gray-500 transition text-[10px] leading-none">Ctrl+v</p>
|
|
61
|
+
</div>
|
|
62
|
+
</button>
|
|
63
|
+
<Skeleton v-else type="image" class="w-14 h-14" />
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="grid grid-cols-[1fr_52px_1fr]">
|
|
67
|
+
<span class="pb-2 text-sm font-medium text-gray-500 dark:text-gray-400">{{ $t('Original image') }}</span>
|
|
68
|
+
<div></div>
|
|
69
|
+
<span class="pb-2 text-sm font-medium text-gray-500 dark:text-gray-400">{{ $t('Generated image') }}</span>
|
|
70
|
+
<div
|
|
71
|
+
class="rounded-xl overflow-hidden bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 aspect-[4/3] flex items-center justify-center"
|
|
72
|
+
:class="requestAttachmentFilesUrls.length ? 'cursor-zoom-in' : ''"
|
|
73
|
+
@click="requestAttachmentFilesUrls.length && zoomImage(requestAttachmentFilesUrls[0])"
|
|
74
|
+
>
|
|
75
|
+
<img
|
|
76
|
+
v-if="requestAttachmentFilesUrls.length"
|
|
77
|
+
:src="requestAttachmentFilesUrls[0]"
|
|
78
|
+
class="w-full h-full object-cover"
|
|
86
79
|
/>
|
|
80
|
+
<span v-else class="text-gray-400 dark:text-gray-500 text-sm">{{ $t('No image') }}</span>
|
|
87
81
|
</div>
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<div role="status" class="absolute -translate-x-1/2 -translate-y-1/2 top-2/4 left-1/2">
|
|
95
|
-
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
|
|
96
|
-
<span class="sr-only">{{ $t('Loading...') }}</span>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
83
|
+
<div class="flex items-center justify-center">
|
|
84
|
+
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
85
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
|
86
|
+
</svg>
|
|
87
|
+
</div>
|
|
99
88
|
|
|
100
|
-
|
|
101
|
-
<div class="text-gray-800 dark:text-gray-100 text-lg font-semibold"
|
|
102
|
-
v-if="!historicalAverage"
|
|
103
|
-
>
|
|
104
|
-
{{ formatTime(loadingTimer) }} {{ $t('passed...') }}
|
|
105
|
-
</div>
|
|
106
|
-
<div class="w-64" v-else>
|
|
107
|
-
<ProgressBar
|
|
108
|
-
class="absolute max-w-full"
|
|
109
|
-
:currentValue="loadingTimer < historicalAverage ? loadingTimer : historicalAverage"
|
|
110
|
-
:minValue="0"
|
|
111
|
-
:maxValue="historicalAverage"
|
|
112
|
-
:showValues="false"
|
|
113
|
-
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (
|
|
114
|
-
loadingTimer < historicalAverage ? loadingTimer : historicalAverage
|
|
115
|
-
) / historicalAverage * 100) }% )`"
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
89
|
+
<div class="relative aspect-[4/3]">
|
|
119
90
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
91
|
+
<div
|
|
92
|
+
v-if="loading"
|
|
93
|
+
class="spinner absolute flex items-center justify-center inset-0 z-40 rounded-xl">
|
|
94
|
+
<Spinner class="w-8 h-8" />
|
|
95
|
+
</div>
|
|
96
|
+
<div v-if="loadingTimer" class=" spinner-description absolute inset-0 z-40 flex items-center justify-center">
|
|
97
|
+
<div class="text-gray-800 dark:text-gray-100 text-base font-semibold mt-20" v-if="!historicalAverage">
|
|
98
|
+
{{ formatTime(loadingTimer) }} {{ $t('passed...') }}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="w-48" v-else>
|
|
101
|
+
<ProgressBar
|
|
102
|
+
class="max-w-full"
|
|
103
|
+
:currentValue="loadingTimer < historicalAverage ? loadingTimer : historicalAverage"
|
|
104
|
+
:minValue="0"
|
|
105
|
+
:maxValue="historicalAverage"
|
|
106
|
+
:showValues="false"
|
|
107
|
+
:progressFormatter="(_value: number, _percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (loadingTimer < historicalAverage ? loadingTimer : historicalAverage) / historicalAverage * 100) }% )`"
|
|
108
|
+
/>
|
|
124
109
|
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div v-if="errorMessage" class="absolute inset-0 z-40 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 rounded-xl">
|
|
113
|
+
<div class="text-red-500 dark:text-red-400 font-semibold text-sm text-center px-4">{{ errorMessage }}</div>
|
|
114
|
+
</div>
|
|
125
115
|
|
|
126
|
-
|
|
127
|
-
<div
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<button @click="generateImages" type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4
|
|
140
|
-
focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center
|
|
141
|
-
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 ms-2">{{ $t('Generate images') }}</button>
|
|
142
|
-
|
|
143
|
-
</div>
|
|
144
|
-
|
|
116
|
+
<div id="gallery" class="relative w-full h-full" data-carousel="static">
|
|
117
|
+
<div
|
|
118
|
+
class="relative w-full h-full overflow-hidden rounded-xl"
|
|
119
|
+
:class="images.length > 0 ? 'cursor-zoom-in' : ''"
|
|
120
|
+
@click="zoomCurrentImage"
|
|
121
|
+
>
|
|
122
|
+
<div v-for="(img, index) in images" :key="index" class="hidden duration-700 ease-in-out" data-carousel-item>
|
|
123
|
+
<img :src="img" class="absolute block w-full h-full object-cover -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2"
|
|
124
|
+
:alt="`Generated image ${index + 1}`"
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
<div v-if="images.length === 0" class="w-full h-full flex items-center justify-center border-2 border-dashed border-gray-200 dark:border-gray-600 rounded-xl bg-gray-50 dark:bg-gray-700">
|
|
128
|
+
<span v-if="!loadingTimer" class="text-gray-400 dark:text-gray-500 text-sm">{{ $t('Your generated image will appear here') }}</span>
|
|
145
129
|
</div>
|
|
146
|
-
<!-- Slider controls -->
|
|
147
|
-
<button type="button" class="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
|
148
|
-
@click="slide(-1)"
|
|
149
|
-
:disabled="images.length === 0"
|
|
150
|
-
>
|
|
151
|
-
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none ">
|
|
152
|
-
<svg class="w-4 h-4 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"
|
|
153
|
-
:class="{
|
|
154
|
-
'text-gray-800 dark:text-gray-200': images.length > 0,
|
|
155
|
-
'text-gray-200 dark:text-gray-800': images.length === 0
|
|
156
|
-
}"
|
|
157
|
-
>
|
|
158
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1 1 5l4 4"/>
|
|
159
|
-
</svg>
|
|
160
|
-
<span class="sr-only">{{ $t('Previous') }}</span>
|
|
161
|
-
</span>
|
|
162
|
-
</button>
|
|
163
|
-
<button type="button" class="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none "
|
|
164
|
-
:disabled="images.length === 0"
|
|
165
|
-
@click="slide(1)"
|
|
166
|
-
>
|
|
167
|
-
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none ">
|
|
168
|
-
<svg class="w-4 h-4 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"
|
|
169
|
-
:class="{
|
|
170
|
-
'text-gray-800 dark:text-gray-200': images.length > 0,
|
|
171
|
-
'text-gray-200 dark:text-gray-800': images.length === 0
|
|
172
|
-
}"
|
|
173
|
-
>
|
|
174
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
175
|
-
</svg>
|
|
176
|
-
<span class="sr-only">{{ $t('Next') }}</span>
|
|
177
|
-
</span>
|
|
178
|
-
</button>
|
|
179
|
-
|
|
180
|
-
|
|
181
130
|
</div>
|
|
182
131
|
</div>
|
|
183
132
|
</div>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
>{{ $t('
|
|
133
|
+
<div class="col-start-3 flex items-center justify-center gap-3 pt-3">
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
@click="slide(-1)"
|
|
137
|
+
:disabled="images.length === 0"
|
|
138
|
+
class="inline-flex items-center justify-center w-8 h-8 rounded-full border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-sm"
|
|
139
|
+
>
|
|
140
|
+
<svg class="w-4 h-4 text-gray-700 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
141
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1 1 5l4 4"/>
|
|
142
|
+
</svg>
|
|
143
|
+
<span class="sr-only">{{ $t('Previous') }}</span>
|
|
144
|
+
</button>
|
|
145
|
+
<button type="button" class="inline-flex items-center justify-center w-8 h-8 rounded-full border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-sm"
|
|
146
|
+
:disabled="loading"
|
|
147
|
+
@click="slide(1)"
|
|
148
|
+
>
|
|
149
|
+
<svg class="w-4 h-4 text-gray-700 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
150
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
151
|
+
</svg>
|
|
152
|
+
<span class="sr-only">{{ $t('Next') }}</span>
|
|
153
|
+
</button>
|
|
195
154
|
</div>
|
|
155
|
+
</div>
|
|
196
156
|
</div>
|
|
157
|
+
<!-- Modal footer -->
|
|
158
|
+
<div class="flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
|
159
|
+
<div class="flex items-center gap-2">
|
|
160
|
+
<Button
|
|
161
|
+
@click="confirmImage"
|
|
162
|
+
:disabled="loading || images.length === 0"
|
|
163
|
+
variant="primary"
|
|
164
|
+
>{{ $t('Use image') }}</Button>
|
|
165
|
+
<Button
|
|
166
|
+
@click="() => { stopGeneration = true; emit('close') }"
|
|
167
|
+
variant="secondary"
|
|
168
|
+
>{{ $t('Cancel') }}</Button>
|
|
169
|
+
</div>
|
|
170
|
+
<Button
|
|
171
|
+
@click="generateImages"
|
|
172
|
+
:disabled="loading"
|
|
173
|
+
variant="primary"
|
|
174
|
+
>{{ $t('Generate images') }}</Button>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
</div>
|
|
197
178
|
</div>
|
|
198
179
|
</div>
|
|
199
180
|
|
|
200
|
-
|
|
201
|
-
|
|
181
|
+
<div
|
|
182
|
+
v-if="zoomedImage"
|
|
183
|
+
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 cursor-zoom-out"
|
|
184
|
+
@click="closeZoom"
|
|
185
|
+
>
|
|
186
|
+
<img
|
|
187
|
+
:src="zoomedImage"
|
|
188
|
+
class="max-w-[90vw] max-h-[90vh] rounded-lg object-contain select-none"
|
|
189
|
+
/>
|
|
190
|
+
<button
|
|
191
|
+
class="absolute top-4 right-4 text-white/70 hover:text-white w-9 h-9 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition"
|
|
192
|
+
>
|
|
193
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 14 14">
|
|
194
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
202
198
|
|
|
203
199
|
</template>
|
|
204
200
|
|
|
205
201
|
<script setup lang="ts">
|
|
206
202
|
|
|
207
|
-
import { ref, onMounted, nextTick, Ref,
|
|
203
|
+
import { ref, onMounted, nextTick, Ref, onUnmounted } from 'vue'
|
|
208
204
|
import { Carousel } from 'flowbite';
|
|
209
205
|
import { callAdminForthApi } from '@/utils';
|
|
210
206
|
import { useI18n } from 'vue-i18n';
|
|
211
207
|
import adminforth from '@/adminforth';
|
|
208
|
+
import { ProgressBar, Button } from '@/afcl';
|
|
209
|
+
import * as Handlebars from 'handlebars';
|
|
212
210
|
import { ProgressBar } from '@/afcl';
|
|
213
211
|
import { IconCloseOutline } from '@iconify-prerendered/vue-flowbite';
|
|
214
|
-
import { Tooltip, Skeleton } from '@/afcl'
|
|
212
|
+
import { Tooltip, Skeleton, Spinner } from '@/afcl'
|
|
215
213
|
import { useRoute } from 'vue-router';
|
|
216
214
|
|
|
217
215
|
const { t: $t, t } = useI18n();
|
|
@@ -468,29 +466,25 @@ async function generateImages() {
|
|
|
468
466
|
loading.value = false;
|
|
469
467
|
}
|
|
470
468
|
|
|
471
|
-
import mediumZoom from 'medium-zoom'
|
|
472
|
-
|
|
473
469
|
const zoomedImage = ref(null)
|
|
474
|
-
const zoomedImg = ref(null)
|
|
475
470
|
|
|
476
471
|
function zoomImage(img) {
|
|
477
472
|
zoomedImage.value = img
|
|
478
473
|
}
|
|
479
474
|
|
|
475
|
+
function zoomCurrentImage() {
|
|
476
|
+
if (images.value.length === 0) return;
|
|
477
|
+
const index = caurosel.value?.getActiveItem()?.position || 0;
|
|
478
|
+
zoomImage(images.value[index]);
|
|
479
|
+
}
|
|
480
|
+
|
|
480
481
|
function closeZoom() {
|
|
481
482
|
zoomedImage.value = null
|
|
482
483
|
}
|
|
483
484
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
mediumZoom(zoomedImg.value, {
|
|
488
|
-
margin: 24,
|
|
489
|
-
background: 'rgba(0, 0, 0, 0.9)',
|
|
490
|
-
scrollOffset: 150
|
|
491
|
-
}).show()
|
|
492
|
-
}
|
|
493
|
-
})
|
|
485
|
+
function onZoomKeyDown(e: KeyboardEvent) {
|
|
486
|
+
if (e.key === 'Escape') closeZoom();
|
|
487
|
+
}
|
|
494
488
|
|
|
495
489
|
async function handleAddFile(event, clipboardFile = null) {
|
|
496
490
|
if (clipboardFile) {
|
|
@@ -611,9 +605,11 @@ function renameFile(file, newName) {
|
|
|
611
605
|
|
|
612
606
|
onMounted(() => {
|
|
613
607
|
document.addEventListener('paste', uploadImageOnPaste);
|
|
608
|
+
document.addEventListener('keydown', onZoomKeyDown);
|
|
614
609
|
});
|
|
615
610
|
|
|
616
611
|
onUnmounted(() => {
|
|
617
612
|
document.removeEventListener('paste', uploadImageOnPaste);
|
|
613
|
+
document.removeEventListener('keydown', onZoomKeyDown);
|
|
618
614
|
});
|
|
619
615
|
</script>
|
|
@@ -1,217 +1,215 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
3
|
<!-- Main modal -->
|
|
4
|
-
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden fixed
|
|
5
|
-
<div class="relative p-4 w-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@click="zoomImage(img)"
|
|
44
|
-
/>
|
|
45
|
-
<div
|
|
46
|
-
class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
|
|
47
|
-
@click="removeFileFromList(key)"
|
|
48
|
-
>
|
|
49
|
-
<Tooltip class="absolute top-0 end-0">
|
|
50
|
-
<div>
|
|
51
|
-
<div class="w-4 h-4 absolute"></div>
|
|
52
|
-
<IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
|
|
53
|
-
</div>
|
|
54
|
-
<template #tooltip>
|
|
55
|
-
Remove file
|
|
56
|
-
</template>
|
|
57
|
-
</Tooltip>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<input
|
|
61
|
-
ref="fileInput"
|
|
62
|
-
class="hidden"
|
|
63
|
-
type="file"
|
|
64
|
-
@change="handleAddFile"
|
|
65
|
-
accept="image/*"
|
|
66
|
-
/>
|
|
67
|
-
<button v-if="!uploading" @click="fileInput?.click()" type="button" class="relative group hover:border-gray-500 transition border-gray-300 flex items-center justify-center w-20 h-20 border-2 border-dashed rounded-md">
|
|
68
|
-
<div class="flex flex-col items-center justify-center gap-2 mt-4 mb-4">
|
|
69
|
-
<IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-6 h-6 text-gray-300 hover:text-gray-300" />
|
|
70
|
-
<p class="text-gray-300 group-hover:text-gray-500 transition bottom-0">Ctrl + v</p>
|
|
71
|
-
</div>
|
|
72
|
-
</button>
|
|
73
|
-
<Skeleton v-else type="image" class="w-20 h-20" />
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<!-- Fullscreen Modal -->
|
|
4
|
+
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden fixed inset-0 z-50 flex justify-center items-center w-full h-full bg-black/50">
|
|
5
|
+
<div class="relative p-4 w-11/12 max-w-5xl max-h-full ">
|
|
6
|
+
<!-- Modal content -->
|
|
7
|
+
<div class="relative bg-white rounded-xl shadow-2xl dark:bg-gray-800">
|
|
8
|
+
<!-- Header -->
|
|
9
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
10
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
11
|
+
{{ $t('Generate image with AI') }}
|
|
12
|
+
</h3>
|
|
13
|
+
<button type="button"
|
|
14
|
+
@click="() => {stopGeneration = true; emit('close') }"
|
|
15
|
+
class="text-gray-400 bg-transparent hover:bg-gray-100 hover:text-gray-900 rounded-lg w-8 h-8 inline-flex justify-center items-center dark:hover:bg-gray-700 dark:hover:text-white transition" >
|
|
16
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
17
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
18
|
+
</svg>
|
|
19
|
+
<span class="sr-only">{{ $t('Close modal') }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
<!-- Modal body -->
|
|
23
|
+
<div class="px-6 py-5 space-y-4">
|
|
24
|
+
<!-- PROMPT TEXTAREA -->
|
|
25
|
+
<!-- Textarea -->
|
|
26
|
+
<textarea
|
|
27
|
+
rows="3"
|
|
28
|
+
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-200 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 resize-none"
|
|
29
|
+
:placeholder="$t('Prompt which will be passed to AI network')"
|
|
30
|
+
v-model="prompt"
|
|
31
|
+
:title="$t('Prompt which will be passed to AI network')"
|
|
32
|
+
></textarea>
|
|
33
|
+
|
|
34
|
+
<!-- Thumbnails -->
|
|
35
|
+
<div class="flex flex-wrap gap-2">
|
|
36
|
+
<div class="group relative" v-for="(img, key) in requestAttachmentFilesUrls" :key="key">
|
|
37
|
+
<img
|
|
38
|
+
:src="img"
|
|
39
|
+
class="w-14 h-14 object-cover rounded-lg cursor-pointer border border-gray-200 hover:border-blue-500 transition"
|
|
40
|
+
:alt="`Attachment ${key + 1}`"
|
|
41
|
+
@click="zoomImage(img)"
|
|
42
|
+
/>
|
|
77
43
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@click.self="closeZoom"
|
|
44
|
+
class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
|
|
45
|
+
@click="removeFileFromList(key)"
|
|
81
46
|
>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
47
|
+
<Tooltip class="absolute top-0 end-0">
|
|
48
|
+
<div>
|
|
49
|
+
<div class="w-4 h-4 absolute"></div>
|
|
50
|
+
<IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
|
|
51
|
+
</div>
|
|
52
|
+
<template #tooltip>Remove file</template>
|
|
53
|
+
</Tooltip>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<input ref="fileInput" class="hidden" type="file" @change="handleAddFile" accept="image/*" />
|
|
57
|
+
<button v-if="!uploading" @click="fileInput?.click()" type="button" :disabled="requestAttachmentFilesUrls.length >= 1" class=" upload-btn relative group hover:border-gray-400 disabled:opacity-40 disabled:cursor-not-allowed transition border-gray-300 flex items-center justify-center w-14 h-14 border-2 border-dashed rounded-lg">
|
|
58
|
+
<div class="flex flex-col items-center justify-center gap-1">
|
|
59
|
+
<IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-5 h-5 text-gray-300" />
|
|
60
|
+
<p class="text-gray-300 group-hover:text-gray-500 transition text-[10px] leading-none">Ctrl+v</p>
|
|
61
|
+
</div>
|
|
62
|
+
</button>
|
|
63
|
+
<Skeleton v-else type="image" class="w-14 h-14" />
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="grid grid-cols-[1fr_52px_1fr]">
|
|
67
|
+
<span class="pb-2 text-sm font-medium text-gray-500 dark:text-gray-400">{{ $t('Original image') }}</span>
|
|
68
|
+
<div></div>
|
|
69
|
+
<span class="pb-2 text-sm font-medium text-gray-500 dark:text-gray-400">{{ $t('Generated image') }}</span>
|
|
70
|
+
<div
|
|
71
|
+
class="rounded-xl overflow-hidden bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 aspect-[4/3] flex items-center justify-center"
|
|
72
|
+
:class="requestAttachmentFilesUrls.length ? 'cursor-zoom-in' : ''"
|
|
73
|
+
@click="requestAttachmentFilesUrls.length && zoomImage(requestAttachmentFilesUrls[0])"
|
|
74
|
+
>
|
|
75
|
+
<img
|
|
76
|
+
v-if="requestAttachmentFilesUrls.length"
|
|
77
|
+
:src="requestAttachmentFilesUrls[0]"
|
|
78
|
+
class="w-full h-full object-cover"
|
|
86
79
|
/>
|
|
80
|
+
<span v-else class="text-gray-400 dark:text-gray-500 text-sm">{{ $t('No image') }}</span>
|
|
87
81
|
</div>
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<div role="status" class="absolute -translate-x-1/2 -translate-y-1/2 top-2/4 left-1/2">
|
|
95
|
-
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
|
|
96
|
-
<span class="sr-only">{{ $t('Loading...') }}</span>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
83
|
+
<div class="flex items-center justify-center">
|
|
84
|
+
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
85
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
|
86
|
+
</svg>
|
|
87
|
+
</div>
|
|
99
88
|
|
|
100
|
-
|
|
101
|
-
<div class="text-gray-800 dark:text-gray-100 text-lg font-semibold"
|
|
102
|
-
v-if="!historicalAverage"
|
|
103
|
-
>
|
|
104
|
-
{{ formatTime(loadingTimer) }} {{ $t('passed...') }}
|
|
105
|
-
</div>
|
|
106
|
-
<div class="w-64" v-else>
|
|
107
|
-
<ProgressBar
|
|
108
|
-
class="absolute max-w-full"
|
|
109
|
-
:currentValue="loadingTimer < historicalAverage ? loadingTimer : historicalAverage"
|
|
110
|
-
:minValue="0"
|
|
111
|
-
:maxValue="historicalAverage"
|
|
112
|
-
:showValues="false"
|
|
113
|
-
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (
|
|
114
|
-
loadingTimer < historicalAverage ? loadingTimer : historicalAverage
|
|
115
|
-
) / historicalAverage * 100) }% )`"
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
89
|
+
<div class="relative aspect-[4/3]">
|
|
119
90
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
91
|
+
<div
|
|
92
|
+
v-if="loading"
|
|
93
|
+
class="spinner absolute flex items-center justify-center inset-0 z-40 rounded-xl">
|
|
94
|
+
<Spinner class="w-8 h-8" />
|
|
95
|
+
</div>
|
|
96
|
+
<div v-if="loadingTimer" class=" spinner-description absolute inset-0 z-40 flex items-center justify-center">
|
|
97
|
+
<div class="text-gray-800 dark:text-gray-100 text-base font-semibold mt-20" v-if="!historicalAverage">
|
|
98
|
+
{{ formatTime(loadingTimer) }} {{ $t('passed...') }}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="w-48" v-else>
|
|
101
|
+
<ProgressBar
|
|
102
|
+
class="max-w-full"
|
|
103
|
+
:currentValue="loadingTimer < historicalAverage ? loadingTimer : historicalAverage"
|
|
104
|
+
:minValue="0"
|
|
105
|
+
:maxValue="historicalAverage"
|
|
106
|
+
:showValues="false"
|
|
107
|
+
:progressFormatter="(_value: number, _percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (loadingTimer < historicalAverage ? loadingTimer : historicalAverage) / historicalAverage * 100) }% )`"
|
|
108
|
+
/>
|
|
124
109
|
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div v-if="errorMessage" class="absolute inset-0 z-40 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 rounded-xl">
|
|
113
|
+
<div class="text-red-500 dark:text-red-400 font-semibold text-sm text-center px-4">{{ errorMessage }}</div>
|
|
114
|
+
</div>
|
|
125
115
|
|
|
126
|
-
|
|
127
|
-
<div
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<button @click="generateImages" type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4
|
|
140
|
-
focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center
|
|
141
|
-
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 ms-2">{{ $t('Generate images') }}</button>
|
|
142
|
-
|
|
143
|
-
</div>
|
|
144
|
-
|
|
116
|
+
<div id="gallery" class="relative w-full h-full" data-carousel="static">
|
|
117
|
+
<div
|
|
118
|
+
class="relative w-full h-full overflow-hidden rounded-xl"
|
|
119
|
+
:class="images.length > 0 ? 'cursor-zoom-in' : ''"
|
|
120
|
+
@click="zoomCurrentImage"
|
|
121
|
+
>
|
|
122
|
+
<div v-for="(img, index) in images" :key="index" class="hidden duration-700 ease-in-out" data-carousel-item>
|
|
123
|
+
<img :src="img" class="absolute block w-full h-full object-cover -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2"
|
|
124
|
+
:alt="`Generated image ${index + 1}`"
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
<div v-if="images.length === 0" class="w-full h-full flex items-center justify-center border-2 border-dashed border-gray-200 dark:border-gray-600 rounded-xl bg-gray-50 dark:bg-gray-700">
|
|
128
|
+
<span v-if="!loadingTimer" class="text-gray-400 dark:text-gray-500 text-sm">{{ $t('Your generated image will appear here') }}</span>
|
|
145
129
|
</div>
|
|
146
|
-
<!-- Slider controls -->
|
|
147
|
-
<button type="button" class="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
|
148
|
-
@click="slide(-1)"
|
|
149
|
-
:disabled="images.length === 0"
|
|
150
|
-
>
|
|
151
|
-
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none ">
|
|
152
|
-
<svg class="w-4 h-4 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"
|
|
153
|
-
:class="{
|
|
154
|
-
'text-gray-800 dark:text-gray-200': images.length > 0,
|
|
155
|
-
'text-gray-200 dark:text-gray-800': images.length === 0
|
|
156
|
-
}"
|
|
157
|
-
>
|
|
158
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1 1 5l4 4"/>
|
|
159
|
-
</svg>
|
|
160
|
-
<span class="sr-only">{{ $t('Previous') }}</span>
|
|
161
|
-
</span>
|
|
162
|
-
</button>
|
|
163
|
-
<button type="button" class="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none "
|
|
164
|
-
:disabled="images.length === 0"
|
|
165
|
-
@click="slide(1)"
|
|
166
|
-
>
|
|
167
|
-
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none ">
|
|
168
|
-
<svg class="w-4 h-4 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"
|
|
169
|
-
:class="{
|
|
170
|
-
'text-gray-800 dark:text-gray-200': images.length > 0,
|
|
171
|
-
'text-gray-200 dark:text-gray-800': images.length === 0
|
|
172
|
-
}"
|
|
173
|
-
>
|
|
174
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
175
|
-
</svg>
|
|
176
|
-
<span class="sr-only">{{ $t('Next') }}</span>
|
|
177
|
-
</span>
|
|
178
|
-
</button>
|
|
179
|
-
|
|
180
|
-
|
|
181
130
|
</div>
|
|
182
131
|
</div>
|
|
183
132
|
</div>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
>{{ $t('
|
|
133
|
+
<div class="col-start-3 flex items-center justify-center gap-3 pt-3">
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
@click="slide(-1)"
|
|
137
|
+
:disabled="images.length === 0"
|
|
138
|
+
class="inline-flex items-center justify-center w-8 h-8 rounded-full border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-sm"
|
|
139
|
+
>
|
|
140
|
+
<svg class="w-4 h-4 text-gray-700 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
141
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1 1 5l4 4"/>
|
|
142
|
+
</svg>
|
|
143
|
+
<span class="sr-only">{{ $t('Previous') }}</span>
|
|
144
|
+
</button>
|
|
145
|
+
<button type="button" class="inline-flex items-center justify-center w-8 h-8 rounded-full border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-sm"
|
|
146
|
+
:disabled="loading"
|
|
147
|
+
@click="slide(1)"
|
|
148
|
+
>
|
|
149
|
+
<svg class="w-4 h-4 text-gray-700 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
150
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
151
|
+
</svg>
|
|
152
|
+
<span class="sr-only">{{ $t('Next') }}</span>
|
|
153
|
+
</button>
|
|
195
154
|
</div>
|
|
155
|
+
</div>
|
|
196
156
|
</div>
|
|
157
|
+
<!-- Modal footer -->
|
|
158
|
+
<div class="flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
|
159
|
+
<div class="flex items-center gap-2">
|
|
160
|
+
<Button
|
|
161
|
+
@click="confirmImage"
|
|
162
|
+
:disabled="loading || images.length === 0"
|
|
163
|
+
variant="primary"
|
|
164
|
+
>{{ $t('Use image') }}</Button>
|
|
165
|
+
<Button
|
|
166
|
+
@click="() => { stopGeneration = true; emit('close') }"
|
|
167
|
+
variant="secondary"
|
|
168
|
+
>{{ $t('Cancel') }}</Button>
|
|
169
|
+
</div>
|
|
170
|
+
<Button
|
|
171
|
+
@click="generateImages"
|
|
172
|
+
:disabled="loading"
|
|
173
|
+
variant="primary"
|
|
174
|
+
>{{ $t('Generate images') }}</Button>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
</div>
|
|
197
178
|
</div>
|
|
198
179
|
</div>
|
|
199
180
|
|
|
200
|
-
|
|
201
|
-
|
|
181
|
+
<div
|
|
182
|
+
v-if="zoomedImage"
|
|
183
|
+
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 cursor-zoom-out"
|
|
184
|
+
@click="closeZoom"
|
|
185
|
+
>
|
|
186
|
+
<img
|
|
187
|
+
:src="zoomedImage"
|
|
188
|
+
class="max-w-[90vw] max-h-[90vh] rounded-lg object-contain select-none"
|
|
189
|
+
/>
|
|
190
|
+
<button
|
|
191
|
+
class="absolute top-4 right-4 text-white/70 hover:text-white w-9 h-9 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition"
|
|
192
|
+
>
|
|
193
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 14 14">
|
|
194
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
202
198
|
|
|
203
199
|
</template>
|
|
204
200
|
|
|
205
201
|
<script setup lang="ts">
|
|
206
202
|
|
|
207
|
-
import { ref, onMounted, nextTick, Ref,
|
|
203
|
+
import { ref, onMounted, nextTick, Ref, onUnmounted } from 'vue'
|
|
208
204
|
import { Carousel } from 'flowbite';
|
|
209
205
|
import { callAdminForthApi } from '@/utils';
|
|
210
206
|
import { useI18n } from 'vue-i18n';
|
|
211
207
|
import adminforth from '@/adminforth';
|
|
208
|
+
import { ProgressBar, Button } from '@/afcl';
|
|
209
|
+
import * as Handlebars from 'handlebars';
|
|
212
210
|
import { ProgressBar } from '@/afcl';
|
|
213
211
|
import { IconCloseOutline } from '@iconify-prerendered/vue-flowbite';
|
|
214
|
-
import { Tooltip, Skeleton } from '@/afcl'
|
|
212
|
+
import { Tooltip, Skeleton, Spinner } from '@/afcl'
|
|
215
213
|
import { useRoute } from 'vue-router';
|
|
216
214
|
|
|
217
215
|
const { t: $t, t } = useI18n();
|
|
@@ -468,29 +466,25 @@ async function generateImages() {
|
|
|
468
466
|
loading.value = false;
|
|
469
467
|
}
|
|
470
468
|
|
|
471
|
-
import mediumZoom from 'medium-zoom'
|
|
472
|
-
|
|
473
469
|
const zoomedImage = ref(null)
|
|
474
|
-
const zoomedImg = ref(null)
|
|
475
470
|
|
|
476
471
|
function zoomImage(img) {
|
|
477
472
|
zoomedImage.value = img
|
|
478
473
|
}
|
|
479
474
|
|
|
475
|
+
function zoomCurrentImage() {
|
|
476
|
+
if (images.value.length === 0) return;
|
|
477
|
+
const index = caurosel.value?.getActiveItem()?.position || 0;
|
|
478
|
+
zoomImage(images.value[index]);
|
|
479
|
+
}
|
|
480
|
+
|
|
480
481
|
function closeZoom() {
|
|
481
482
|
zoomedImage.value = null
|
|
482
483
|
}
|
|
483
484
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
mediumZoom(zoomedImg.value, {
|
|
488
|
-
margin: 24,
|
|
489
|
-
background: 'rgba(0, 0, 0, 0.9)',
|
|
490
|
-
scrollOffset: 150
|
|
491
|
-
}).show()
|
|
492
|
-
}
|
|
493
|
-
})
|
|
485
|
+
function onZoomKeyDown(e: KeyboardEvent) {
|
|
486
|
+
if (e.key === 'Escape') closeZoom();
|
|
487
|
+
}
|
|
494
488
|
|
|
495
489
|
async function handleAddFile(event, clipboardFile = null) {
|
|
496
490
|
if (clipboardFile) {
|
|
@@ -611,9 +605,11 @@ function renameFile(file, newName) {
|
|
|
611
605
|
|
|
612
606
|
onMounted(() => {
|
|
613
607
|
document.addEventListener('paste', uploadImageOnPaste);
|
|
608
|
+
document.addEventListener('keydown', onZoomKeyDown);
|
|
614
609
|
});
|
|
615
610
|
|
|
616
611
|
onUnmounted(() => {
|
|
617
612
|
document.removeEventListener('paste', uploadImageOnPaste);
|
|
613
|
+
document.removeEventListener('keydown', onZoomKeyDown);
|
|
618
614
|
});
|
|
619
615
|
</script>
|