fix
This commit is contained in:
@@ -9,7 +9,6 @@ import Skeleton from 'primevue/skeleton'
|
|||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import ConfirmDialog from 'primevue/confirmdialog'
|
import ConfirmDialog from 'primevue/confirmdialog'
|
||||||
import { useConfirm } from 'primevue/useconfirm'
|
import { useConfirm } from 'primevue/useconfirm'
|
||||||
import Image from 'primevue/image'
|
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -129,6 +128,14 @@ const removeGeneration = (gen) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Image Preview ---
|
||||||
|
const isImagePreviewVisible = ref(false)
|
||||||
|
const previewImage = ref(null)
|
||||||
|
const openImagePreview = (url) => {
|
||||||
|
previewImage.value = { url }
|
||||||
|
isImagePreviewVisible.value = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -183,9 +190,11 @@ const removeGeneration = (gen) => {
|
|||||||
<div v-for="gen in generations" :key="gen.id"
|
<div v-for="gen in generations" :key="gen.id"
|
||||||
class="glass-panel rounded-xl overflow-hidden group relative transition-all hover:bg-white/5">
|
class="glass-panel rounded-xl overflow-hidden group relative transition-all hover:bg-white/5">
|
||||||
|
|
||||||
<div class="aspect-[2/3] w-full bg-slate-800 relative overflow-hidden">
|
<div class="aspect-[2/3] w-full bg-slate-800 relative overflow-hidden cursor-pointer"
|
||||||
<Image :src="gen.result || API_URL + `/assets/${gen.result_list[0]}` + '?thumbnail=true'"
|
@click="gen.result_list && gen.result_list.length > 0 ? openImagePreview(API_URL + '/assets/' + gen.result_list[0]) : null">
|
||||||
preview class="w-full h-full object-cover" imageClass="w-full h-full object-cover" />
|
<img v-if="gen.result_list && gen.result_list.length > 0"
|
||||||
|
:src="gen.result || API_URL + `/assets/${gen.result_list[0]}` + '?thumbnail=true'"
|
||||||
|
class="w-full h-full object-cover" />
|
||||||
|
|
||||||
<!-- Overlay Actions -->
|
<!-- Overlay Actions -->
|
||||||
<div
|
<div
|
||||||
@@ -230,7 +239,7 @@ const removeGeneration = (gen) => {
|
|||||||
:class="selectedGenerations.some(g => g.id === gen.id) ? 'border-violet-500 ring-2 ring-violet-500/30' : 'border-transparent hover:border-white/20'">
|
:class="selectedGenerations.some(g => g.id === gen.id) ? 'border-violet-500 ring-2 ring-violet-500/30' : 'border-transparent hover:border-white/20'">
|
||||||
|
|
||||||
<img v-if="gen.result_list && gen.result_list.length > 0"
|
<img v-if="gen.result_list && gen.result_list.length > 0"
|
||||||
:src="gen.result_list[0].includes('http') ? gen.result_list[0] : (gen.result || API_URL + `/assets/${gen.result_list[0]}` + '?thumbnail=true')"
|
:src="gen.result_list[0].includes('http') ? gen.result_list[0] : (gen.result || API_URL + `/assets/${gen.result_list[0]}`)"
|
||||||
class="w-full h-full object-cover" />
|
class="w-full h-full object-cover" />
|
||||||
<!-- Fallback for no result -->
|
<!-- Fallback for no result -->
|
||||||
<div v-else class="w-full h-full bg-slate-800 flex items-center justify-center text-slate-500">
|
<div v-else class="w-full h-full bg-slate-800 flex items-center justify-center text-slate-500">
|
||||||
@@ -263,6 +272,18 @@ const removeGeneration = (gen) => {
|
|||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Image Preview Modal -->
|
||||||
|
<Dialog v-model:visible="isImagePreviewVisible" modal dismissableMask
|
||||||
|
:style="{ width: '90vw', maxWidth: '1000px', background: 'transparent', boxShadow: 'none' }"
|
||||||
|
:pt="{ root: { class: '!bg-transparent !border-none !shadow-none' }, header: { class: '!hidden' }, content: { class: '!bg-transparent !p-0' } }">
|
||||||
|
<div class="relative flex items-center justify-center" @click="isImagePreviewVisible = false">
|
||||||
|
<img v-if="previewImage" :src="previewImage.url"
|
||||||
|
class="max-w-full max-h-[85vh] object-contain rounded-xl shadow-2xl" />
|
||||||
|
<Button icon="pi pi-times" @click="isImagePreviewVisible = false" rounded text
|
||||||
|
class="!absolute -top-4 -right-4 !text-white !bg-black/50 hover:!bg-black/70 !w-10 !h-10" />
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const historyFirst = ref(0)
|
|||||||
const isSettingsVisible = ref(false)
|
const isSettingsVisible = ref(false)
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
const activeOverlayId = ref(null) // For mobile tap-to-show overlay
|
const activeOverlayId = ref(null) // For mobile tap-to-show overlay
|
||||||
|
const filterCharacter = ref(null) // Character filter for gallery
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
const qualityOptions = ref([
|
const qualityOptions = ref([
|
||||||
@@ -120,6 +121,14 @@ watch([prompt, selectedCharacter, selectedAssets, quality, aspectRatio, sendToTe
|
|||||||
saveSettings()
|
saveSettings()
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
// Watcher for character filter — reload history when filter changes
|
||||||
|
watch(filterCharacter, async () => {
|
||||||
|
historyGenerations.value = []
|
||||||
|
historyTotal.value = 0
|
||||||
|
historyFirst.value = 0
|
||||||
|
await refreshHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// --- Data Loading ---
|
// --- Data Loading ---
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -127,7 +136,7 @@ const loadData = async () => {
|
|||||||
const [charsRes, assetsRes, historyRes] = await Promise.all([
|
const [charsRes, assetsRes, historyRes] = await Promise.all([
|
||||||
dataService.getCharacters(), // Assuming this exists and returns list
|
dataService.getCharacters(), // Assuming this exists and returns list
|
||||||
dataService.getAssets(100, 0, 'all'), // Load a batch of assets
|
dataService.getAssets(100, 0, 'all'), // Load a batch of assets
|
||||||
aiService.getGenerations(historyRows.value, historyFirst.value)
|
aiService.getGenerations(historyRows.value, historyFirst.value, filterCharacter.value?.id)
|
||||||
])
|
])
|
||||||
|
|
||||||
// Characters
|
// Characters
|
||||||
@@ -197,7 +206,7 @@ const loadData = async () => {
|
|||||||
|
|
||||||
const refreshHistory = async () => {
|
const refreshHistory = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await aiService.getGenerations(historyRows.value, 0)
|
const response = await aiService.getGenerations(historyRows.value, 0, filterCharacter.value?.id)
|
||||||
if (response && response.generations) {
|
if (response && response.generations) {
|
||||||
// Update existing items and add new ones at the top
|
// Update existing items and add new ones at the top
|
||||||
const newGenerations = []
|
const newGenerations = []
|
||||||
@@ -352,7 +361,7 @@ const loadMoreHistory = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const nextOffset = historyGenerations.value.length
|
const nextOffset = historyGenerations.value.length
|
||||||
const response = await aiService.getGenerations(historyRows.value, nextOffset)
|
const response = await aiService.getGenerations(historyRows.value, nextOffset, filterCharacter.value?.id)
|
||||||
|
|
||||||
if (response && response.generations) {
|
if (response && response.generations) {
|
||||||
const newGenerations = response.generations.filter(gen =>
|
const newGenerations = response.generations.filter(gen =>
|
||||||
@@ -602,6 +611,32 @@ const confirmAddToAlbum = async () => {
|
|||||||
<span class="text-xs text-slate-500 border-l border-white/10 pl-3">History</span>
|
<span class="text-xs text-slate-500 border-l border-white/10 pl-3">History</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<Dropdown v-model="filterCharacter" :options="characters" optionLabel="name"
|
||||||
|
placeholder="All Characters" showClear
|
||||||
|
class="!w-48 !bg-slate-800/60 !border-white/10 !text-white !rounded-xl !text-sm" :pt="{
|
||||||
|
root: { class: '!bg-slate-800/60 !h-8' },
|
||||||
|
input: { class: '!text-white !text-xs !py-1 !px-2' },
|
||||||
|
trigger: { class: '!text-slate-400 !w-6' },
|
||||||
|
panel: { class: '!bg-slate-800 !border-white/10' },
|
||||||
|
item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white !text-xs !py-1.5' },
|
||||||
|
clearIcon: { class: '!text-slate-400 hover:!text-white' }
|
||||||
|
}">
|
||||||
|
<template #value="slotProps">
|
||||||
|
<div v-if="slotProps.value" class="flex items-center gap-1.5">
|
||||||
|
<img v-if="slotProps.value.avatar_image" :src="API_URL + slotProps.value.avatar_image"
|
||||||
|
class="w-5 h-5 rounded-full object-cover" />
|
||||||
|
<span class="text-xs">{{ slotProps.value.name }}</span>
|
||||||
|
</div>
|
||||||
|
<span v-else class="text-xs text-slate-400">{{ slotProps.placeholder }}</span>
|
||||||
|
</template>
|
||||||
|
<template #option="slotProps">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img v-if="slotProps.option.avatar_image" :src="API_URL + slotProps.option.avatar_image"
|
||||||
|
class="w-6 h-6 rounded-full object-cover" />
|
||||||
|
<span>{{ slotProps.option.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
<Button icon="pi pi-refresh" @click="refreshHistory" rounded text
|
<Button icon="pi pi-refresh" @click="refreshHistory" rounded text
|
||||||
class="!text-slate-400 hover:!bg-white/10 !w-8 !h-8 md:hidden" />
|
class="!text-slate-400 hover:!bg-white/10 !w-8 !h-8 md:hidden" />
|
||||||
<Button icon="pi pi-cog" @click="isSettingsVisible = true" rounded text
|
<Button icon="pi pi-cog" @click="isSettingsVisible = true" rounded text
|
||||||
@@ -790,7 +825,8 @@ const confirmAddToAlbum = async () => {
|
|||||||
class="flex items-center gap-2 mt-2 px-1 animate-in fade-in slide-in-from-top-1">
|
class="flex items-center gap-2 mt-2 px-1 animate-in fade-in slide-in-from-top-1">
|
||||||
<Checkbox v-model="useProfileImage" :binary="true" inputId="use-profile-img" />
|
<Checkbox v-model="useProfileImage" :binary="true" inputId="use-profile-img" />
|
||||||
<label for="use-profile-img"
|
<label for="use-profile-img"
|
||||||
class="text-xs text-slate-300 cursor-pointer select-none">Use Character
|
class="text-xs text-slate-300 cursor-pointer select-none">Use
|
||||||
|
Character
|
||||||
Photo</label>
|
Photo</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -801,7 +837,8 @@ const confirmAddToAlbum = async () => {
|
|||||||
<div @click="openAssetPicker"
|
<div @click="openAssetPicker"
|
||||||
class="w-full bg-slate-800 border border-white/10 rounded-xl p-3 min-h-[46px] cursor-pointer hover:bg-slate-700/50 transition-colors flex flex-wrap gap-2">
|
class="w-full bg-slate-800 border border-white/10 rounded-xl p-3 min-h-[46px] cursor-pointer hover:bg-slate-700/50 transition-colors flex flex-wrap gap-2">
|
||||||
<span v-if="selectedAssets.length === 0"
|
<span v-if="selectedAssets.length === 0"
|
||||||
class="text-slate-400 text-sm py-0.5">Select Assets</span>
|
class="text-slate-400 text-sm py-0.5">Select
|
||||||
|
Assets</span>
|
||||||
<div v-for="asset in selectedAssets" :key="asset.id"
|
<div v-for="asset in selectedAssets" :key="asset.id"
|
||||||
class="px-2 py-1 bg-violet-600/30 border border-violet-500/30 text-violet-200 text-xs rounded-md flex items-center gap-2 animate-in fade-in zoom-in duration-200"
|
class="px-2 py-1 bg-violet-600/30 border border-violet-500/30 text-violet-200 text-xs rounded-md flex items-center gap-2 animate-in fade-in zoom-in duration-200"
|
||||||
@click.stop>
|
@click.stop>
|
||||||
|
|||||||
Reference in New Issue
Block a user