Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/assistant/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,9 @@ export async function generateImage(options: {
const response = await fetch('/bizyair/model/images', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
Authorization: Cookies.get('bizy_token') || '',
...(options as any)?.headers
},
body: JSON.stringify({
prompt: prompt,
Expand Down
41 changes: 32 additions & 9 deletions src/components/community/detail/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@

const showAllTags = ref(false)

// 添加视频检测函数
const isVideoUrl = (url: string) => {
if (!url) return false
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv']
const lowercaseUrl = url.toLowerCase()
return videoExtensions.some(ext => lowercaseUrl.includes(ext))
}

const fetchModelDetail = async () => {
try {
const res = await model_detail({
Expand Down Expand Up @@ -821,15 +829,30 @@
class="flex flex-col gap-4 items-start justify-start relative min-w-[620px] w-[65%] overflow-hidden"
>
<div class="w-full">
<NImageGroup v-if="currentVersion?.cover_urls && currentVersion?.cover_urls.length > 0">
<NImage
v-for="(cover, index) in currentVersion?.cover_urls"
:key="index"
:src="cover"
:preview-src="cover"
height="512px"
/>
</NImageGroup>
<div
v-if="currentVersion?.cover_urls && currentVersion?.cover_urls.length > 0"
class="space-y-4"
>
<div v-for="(cover, index) in currentVersion?.cover_urls" :key="index" class="w-full">
<!-- 视频显示 -->
<video
v-if="isVideoUrl(cover)"
:src="cover"
controls
class="w-full h-auto max-h-[512px] object-contain rounded-lg"
preload="metadata"
/>
<!-- 图片显示 -->
<NImageGroup v-else>
<NImage
:src="cover"
:preview-src="cover"
height="512px"
class="w-full object-contain"
/>
</NImageGroup>
</div>
</div>
<MdPreview
v-if="currentVersion?.intro"
id="previewRef"
Expand Down
88 changes: 69 additions & 19 deletions src/components/community/modules/ModelCard.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Model } from '@/types/model'
// import { Model } from '@/types/model'
import vDefaultPic from '@/components/modules/vDefaultPic.vue'
import vTooltips from '@/components/modules/v-tooltip.vue'
import { sliceString, formatNumber } from '@/utils/tool'
Expand All @@ -18,24 +18,16 @@
const showDialog = ref(false)
const imgSrc = ref('')
const tagsStore = useDictStore()
const props = defineProps({
model: {
type: Object as () => Model | null,
default: null
},
loading: {
type: Boolean,
default: false
},
imageLoaded: {
type: Boolean,
default: false
}
})
const props = defineProps<{
model?: any
imageLoaded?: boolean
loading?: boolean
}>()

const emit = defineEmits(['action', 'image-load', 'image-error'])

// 计算工作流或节点的提示文本
const isHovering = ref(false)

const actionTooltipText = computed(() => {
return props.model?.type === 'Workflow'
? t('community.modelCard.tooltips.loadWorkflow')
Expand Down Expand Up @@ -94,6 +86,41 @@
imgSrc.value = url.includes('?') ? `${url}&t=${timestamp}` : `${url}?t=${timestamp}`
}
})

const isVideo = computed(() => {
if (!imgSrc.value) return false
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv']
const url = imgSrc.value.toLowerCase()
return videoExtensions.some(ext => url.includes(ext))
})

const getVideoThumbnail = (videoUrl: string) => {
if (videoUrl.includes('x-oss-process=video/snapshot')) {
return videoUrl
}
const separator = videoUrl.includes('?') ? '&' : '?'
return `${videoUrl}${separator}x-oss-process=video/snapshot,t_0000,f_jpg,w_300,h_600`
}

const currentMediaSrc = computed(() => {
if (!imgSrc.value) return ''
if (isVideo.value) {
return isHovering.value ? imgSrc.value : getVideoThumbnail(imgSrc.value)
}
return imgSrc.value
})

const handleMouseEnter = () => {
if (isVideo.value) {
isHovering.value = true
}
}

const handleMouseLeave = () => {
if (isVideo.value) {
isHovering.value = false
}
}
</script>

<template>
Expand Down Expand Up @@ -139,17 +166,39 @@
<div
class="relative aspect-[2/3] md:aspect-[3/4] lg:aspect-[2/3] overflow-hidden"
@click.prevent="handleDetail(Number(model?.id), Number(model?.versions?.[0]?.id))"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<div
class="absolute inset-0 bg-gradient-to-br from-[#2a2a2a] to-[#1a1a1a]"
:class="{ 'opacity-0': props.imageLoaded }"
></div>

<!-- 视频显示(悬停时) -->
<video
v-if="isVideo && isHovering && currentMediaSrc"
:src="currentMediaSrc"
class="absolute inset-0 w-full h-full object-cover transition-all duration-300"
:class="{
'opacity-0': !props.imageLoaded,
'opacity-100 group-hover:scale-105': props.imageLoaded
}"
muted
autoplay
loop
preload="metadata"
@loadeddata="handleImageLoad"
@error="handleImageError"
/>

<img
v-if="imgSrc"
:src="imgSrc"
v-else-if="currentMediaSrc"
:src="currentMediaSrc"
:alt="model.versions?.[0]?.version || model.name"
:crossorigin="
typeof imgSrc === 'string' && imgSrc.startsWith('blob:') ? 'anonymous' : undefined
typeof currentMediaSrc === 'string' && currentMediaSrc.startsWith('blob:')
? 'anonymous'
: undefined
"
class="absolute inset-0 w-full h-full object-cover transition-all duration-300"
:class="{
Expand All @@ -159,6 +208,7 @@
@load="handleImageLoad"
@error="handleImageError"
/>

<div v-if="!props.imageLoaded" class="absolute inset-0 flex items-center justify-center">
<vDefaultPic />
</div>
Expand Down
44 changes: 34 additions & 10 deletions src/components/model-select/detail/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
const isLoading = ref(false)
const activeTab = ref<number>()
const showAllTags = ref(false)

// 添加视频检测函数
const isVideoUrl = (url: string) => {
if (!url) return false
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv']
const lowercaseUrl = url.toLowerCase()
return videoExtensions.some(ext => lowercaseUrl.includes(ext))
}

const fetchModelDetail = async () => {
try {
const res = await model_detail({
Expand Down Expand Up @@ -688,15 +697,30 @@
class="flex flex-col gap-4 items-start justify-start relative min-w-[620px] w-[65%] overflow-hidden"
>
<div class="w-full">
<NImageGroup v-if="currentVersion?.cover_urls && currentVersion?.cover_urls.length > 0">
<NImage
v-for="(cover, index) in currentVersion?.cover_urls"
:key="index"
:src="cover"
:preview-src="cover"
height="512px"
/>
</NImageGroup>
<div
v-if="currentVersion?.cover_urls && currentVersion?.cover_urls.length > 0"
class="space-y-4"
>
<div v-for="(cover, index) in currentVersion?.cover_urls" :key="index" class="w-full">
<!-- 视频显示 -->
<video
v-if="isVideoUrl(cover)"
:src="cover"
controls
class="w-full h-auto max-h-[512px] object-contain rounded-lg"
preload="metadata"
/>
<!-- 图片显示 -->
<NImageGroup v-else>
<NImage
:src="cover"
:preview-src="cover"
height="512px"
class="w-full object-contain"
/>
</NImageGroup>
</div>
</div>
<MdPreview
v-if="currentVersion?.intro"
id="previewRef"
Expand Down Expand Up @@ -813,7 +837,7 @@
>
{{ t('community.detail.baseModel') }}
</div>
<div className="flex-1 p-4 border-b border-[rgba(78,78,78,0.50)]">
<div className="flex-1 p-4 border-b border-b-[rgba(78,78,78,0.50)]">
{{ currentVersion?.base_model }}
</div>
</div>
Expand Down
76 changes: 71 additions & 5 deletions src/components/model-select/modules/ModelCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { sliceString, formatNumber } from '@/utils/tool'
import { useModelSelectStore } from '@/stores/modelSelectStore'
import { ref, watch, onMounted } from 'vue'
import { ref, watch, onMounted, computed } from 'vue'
import { useDictStore } from '@/stores/dictStore'

const { t } = useI18n()
Expand All @@ -18,13 +18,13 @@
const modelSelectStore = useModelSelectStore()
const imgSrc = ref('')
const tagsStore = useDictStore()
const isHovering = ref(false)

const props = defineProps({
model: {
type: Object as () => Model | null,
default: null
},

loading: {
type: Boolean,
default: false
Expand All @@ -37,6 +37,48 @@

const emit = defineEmits(['action', 'image-load', 'image-error'])

// 添加计算属性判断是否为视频
const isVideo = computed(() => {
if (!imgSrc.value) return false
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv']
const url = imgSrc.value.toLowerCase()
return videoExtensions.some(ext => url.includes(ext))
})

// 生成视频缩略图URL
const getVideoThumbnail = (videoUrl: string) => {
// 如果URL已经包含OSS处理参数,直接返回
if (videoUrl.includes('x-oss-process=video/snapshot')) {
return videoUrl
}
// 添加OSS视频截图处理参数
const separator = videoUrl.includes('?') ? '&' : '?'
return `${videoUrl}${separator}x-oss-process=video/snapshot,t_000,f_jpg,w_300,h_600`
}

// 当前显示的媒体源
const currentMediaSrc = computed(() => {
if (!imgSrc.value) return ''
if (isVideo.value) {
// 如果是视频且鼠标悬停,返回视频URL,否则返回缩略图
return isHovering.value ? imgSrc.value : getVideoThumbnail(imgSrc.value)
}
return imgSrc.value
})

// 鼠标悬停处理
const handleMouseEnter = () => {
if (isVideo.value) {
isHovering.value = true
}
}

const handleMouseLeave = () => {
if (isVideo.value) {
isHovering.value = false
}
}

const handleImageLoad = (e: Event) => {
emit('image-load', e)
}
Expand Down Expand Up @@ -114,17 +156,40 @@
<div
class="relative aspect-[2/3] md:aspect-[3/4] lg:aspect-[2/3] overflow-hidden"
@click.prevent="handleDetail(Number(model?.id), Number(model?.versions?.[0]?.id))"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<div
class="absolute inset-0 bg-gradient-to-br from-[#2a2a2a] to-[#1a1a1a]"
:class="{ 'opacity-0': props.imageLoaded }"
></div>

<!-- 视频显示(悬停时) -->
<video
v-if="isVideo && isHovering && currentMediaSrc"
:src="currentMediaSrc"
class="absolute inset-0 w-full h-full object-cover transition-all duration-300"
:class="{
'opacity-0': !props.imageLoaded,
'opacity-100 group-hover:scale-105': props.imageLoaded
}"
muted
autoplay
loop
preload="metadata"
@loadeddata="handleImageLoad"
@error="handleImageError"
/>

<!-- 图片显示(包括视频缩略图) -->
<img
v-if="imgSrc"
:src="imgSrc"
v-else-if="currentMediaSrc"
:src="currentMediaSrc"
:alt="model.versions?.[0]?.version || model.name"
:crossorigin="
typeof imgSrc === 'string' && imgSrc.startsWith('blob:') ? 'anonymous' : undefined
typeof currentMediaSrc === 'string' && currentMediaSrc.startsWith('blob:')
? 'anonymous'
: undefined
"
class="absolute inset-0 w-full h-full object-cover transition-all duration-300"
:class="{
Expand All @@ -134,6 +199,7 @@
@load="handleImageLoad"
@error="handleImageError"
/>

<div v-if="!props.imageLoaded" class="absolute inset-0 flex items-center justify-center">
<vDefaultPic />
</div>
Expand Down
Loading
Loading