304 lines
7.3 KiB
Vue
304 lines
7.3 KiB
Vue
<script setup lang="ts">
|
|
import type { ResultBlockMeta } from '~/components/aigc/drawing/index'
|
|
import type { PropType } from 'vue'
|
|
import dayjs from 'dayjs'
|
|
import { get } from 'idb-keyval'
|
|
|
|
const props = defineProps({
|
|
icon: {
|
|
type: String,
|
|
default: 'i-tabler-photo-filled',
|
|
},
|
|
prompt: {
|
|
type: String,
|
|
},
|
|
fid: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
images: {
|
|
type: Array,
|
|
},
|
|
meta: {
|
|
type: Object as PropType<ResultBlockMeta>,
|
|
},
|
|
})
|
|
const emit = defineEmits(['use-reference'])
|
|
|
|
const toast = useToast()
|
|
|
|
const expand_prompt = ref(false)
|
|
const show_meta = ref(true)
|
|
|
|
const cachedImages = ref<string[]>([])
|
|
const cachedImagesInterval = ref<NodeJS.Timeout | null>(null)
|
|
|
|
onMounted(async () => {
|
|
cachedImagesInterval.value = setInterval(async () => {
|
|
const res = ((await get(props.fid)) as string[]) || []
|
|
if (res.length === cachedImages.value.length) return
|
|
cachedImages.value = res
|
|
}, 200)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (cachedImagesInterval.value) {
|
|
clearInterval(cachedImagesInterval.value)
|
|
}
|
|
})
|
|
|
|
const handle_download = (url: string) => {
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = `xsh_ai_drawing-${dayjs(props.meta?.datetime! * 1000).format('YYYY-MM-DD-HH-mm-ss')}.png`
|
|
a.click()
|
|
}
|
|
|
|
const handle_use_reference = async (blob_url: string) => {
|
|
fetch(blob_url)
|
|
.then((res) => res.blob())
|
|
.then((blob) => {
|
|
const file = new File(
|
|
[blob],
|
|
`xsh_drawing-${props.meta?.datetime! * 1000}.png`,
|
|
{ type: 'image/png' }
|
|
)
|
|
emit('use-reference', file)
|
|
})
|
|
.catch(() => {
|
|
toast.add({
|
|
title: '转换失败',
|
|
description: '无法获取图片数据',
|
|
color: 'red',
|
|
icon: 'i-tabler-circle-x',
|
|
})
|
|
})
|
|
}
|
|
|
|
const copyToClipboard = (text: string) => {
|
|
navigator.clipboard
|
|
.writeText(text)
|
|
.then(() => {
|
|
toast.add({
|
|
title: '复制成功',
|
|
description: '已将内容复制到剪贴板',
|
|
color: 'primary',
|
|
icon: 'i-tabler-copy',
|
|
})
|
|
})
|
|
.catch(() => {
|
|
toast.add({
|
|
title: '复制失败',
|
|
description: '无法复制到剪贴板',
|
|
color: 'red',
|
|
icon: 'i-tabler-circle-x',
|
|
})
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="w-full">
|
|
<div class="flex items-center gap-1">
|
|
<UIcon :name="icon" />
|
|
<h1 class="text-sm font-semibold">
|
|
{{ meta.type || 'AI 智能绘图' }}
|
|
</h1>
|
|
<UDivider
|
|
class="flex-1"
|
|
size="sm"
|
|
/>
|
|
<UButton
|
|
color="black"
|
|
size="xs"
|
|
icon="i-tabler-info-circle"
|
|
:variant="show_meta ? 'solid' : 'ghost'"
|
|
:disabled="!meta"
|
|
@click="show_meta = !show_meta"
|
|
></UButton>
|
|
<slot name="header-right" />
|
|
</div>
|
|
<div
|
|
v-if="prompt"
|
|
class="flex items-start gap-2 mt-1 mb-2"
|
|
>
|
|
<UIcon
|
|
name="i-tabler-article"
|
|
class="mt-0.5"
|
|
/>
|
|
<p
|
|
class="text-sm flex-1 text-ellipsis cursor-pointer"
|
|
:class="{
|
|
'line-clamp-1': !expand_prompt,
|
|
'line-clamp-none': expand_prompt,
|
|
}"
|
|
@click="expand_prompt = !expand_prompt"
|
|
>
|
|
{{ prompt }}
|
|
</p>
|
|
<UButton
|
|
color="gray"
|
|
size="xs"
|
|
icon="i-tabler-copy"
|
|
variant="ghost"
|
|
class="-mt-1"
|
|
@click="copyToClipboard(prompt)"
|
|
></UButton>
|
|
</div>
|
|
<div
|
|
v-if="cachedImages.length > 0"
|
|
class="flex items-center overflow-x-auto h-64 gap-2 pb-2 snap-x"
|
|
>
|
|
<div
|
|
class="h-full aspect-auto relative rounded-lg shadow-md overflow-hidden group"
|
|
v-for="(url, i) in cachedImages"
|
|
:key="`${fid}-${i}`"
|
|
>
|
|
<div
|
|
class="absolute inset-0 bg-linear-to-t from-neutral-800/40 to-transparent w-full h-full flex items-end scale-105 opacity-0 group-hover:scale-100 group-hover:opacity-100 transition"
|
|
>
|
|
<div class="w-full flex justify-end gap-1 p-1">
|
|
<UTooltip text="以此图为参考创作">
|
|
<UButton
|
|
color="indigo"
|
|
variant="soft"
|
|
size="2xs"
|
|
icon="i-tabler-copy"
|
|
square
|
|
@click="handle_use_reference(url)"
|
|
></UButton>
|
|
</UTooltip>
|
|
<UTooltip text="下载">
|
|
<UButton
|
|
color="indigo"
|
|
variant="soft"
|
|
size="2xs"
|
|
icon="i-tabler-download"
|
|
square
|
|
@click="handle_download(url)"
|
|
></UButton>
|
|
</UTooltip>
|
|
</div>
|
|
</div>
|
|
<img
|
|
class="result-image"
|
|
:src="useBlobUrlFromB64(url)"
|
|
alt="AI Generated"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else
|
|
class="h-64 aspect-3/4 mb-4 rounded-lg placeholder-gradient flex justify-center items-center"
|
|
>
|
|
<UIcon
|
|
name="i-svg-spinners-tadpole"
|
|
class="text-3xl"
|
|
/>
|
|
</div>
|
|
<Transition
|
|
v-if="meta"
|
|
name="meta"
|
|
>
|
|
<div
|
|
v-if="show_meta"
|
|
class="w-full flex items-center gap-2 flex-wrap whitespace-nowrap pb-2 mt-2"
|
|
>
|
|
<UBadge
|
|
v-if="meta.modal"
|
|
color="black"
|
|
variant="solid"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-tabler-box-seam"
|
|
/>
|
|
{{ meta.modal }}
|
|
</UBadge>
|
|
<UBadge
|
|
v-if="meta.style"
|
|
color="green"
|
|
variant="subtle"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-tabler-christmas-tree"
|
|
/>
|
|
{{ meta.style }}
|
|
</UBadge>
|
|
<UBadge
|
|
v-if="meta.cost"
|
|
color="amber"
|
|
variant="subtle"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-solar-fire-bold"
|
|
/>
|
|
{{ meta.cost }}
|
|
</UBadge>
|
|
<UBadge
|
|
v-if="meta.ratio"
|
|
color="indigo"
|
|
variant="subtle"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-tabler-aspect-ratio"
|
|
/>
|
|
{{ meta.ratio }}
|
|
</UBadge>
|
|
<UBadge
|
|
v-if="meta.id"
|
|
color="indigo"
|
|
variant="subtle"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-tabler-number"
|
|
/>
|
|
{{ meta.id }}
|
|
</UBadge>
|
|
<UBadge
|
|
v-if="meta.datetime"
|
|
color="indigo"
|
|
variant="subtle"
|
|
class="text-[10px] font-bold gap-0.5"
|
|
>
|
|
<UIcon
|
|
class="text-sm"
|
|
name="i-tabler-calendar-month"
|
|
/>
|
|
{{ dayjs(meta.datetime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
|
|
</UBadge>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.meta-enter-active,
|
|
.meta-leave-active {
|
|
@apply transition duration-300;
|
|
}
|
|
|
|
.meta-enter-from,
|
|
.meta-leave-to {
|
|
@apply opacity-0 -translate-y-2;
|
|
}
|
|
|
|
.result-image {
|
|
@apply snap-start;
|
|
@apply w-full h-full object-cover;
|
|
}
|
|
|
|
.placeholder-gradient {
|
|
@apply animate-pulse bg-linear-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800;
|
|
}
|
|
</style>
|