diff --git a/backend/apps/generation/views.py b/backend/apps/generation/views.py index cbe718f..92774d0 100644 --- a/backend/apps/generation/views.py +++ b/backend/apps/generation/views.py @@ -3446,7 +3446,8 @@ def asset_poll_status_view(request, asset_id): except Asset.DoesNotExist: return Response({'error': '素材不存在'}, status=status.HTTP_404_NOT_FOUND) - if asset.remote_asset_id: + # 已经 active 且有 URL 的素材跳过远程查询(避免跨项目素材被误删) + if asset.remote_asset_id and asset.status != 'active': from utils import assets_client from utils.assets_client import AssetsAPIError try: diff --git a/web/src/components/GenerationCard.tsx b/web/src/components/GenerationCard.tsx index 8e3a3e8..3ba65d9 100644 --- a/web/src/components/GenerationCard.tsx +++ b/web/src/components/GenerationCard.tsx @@ -39,9 +39,12 @@ const DownloadIcon = () => ( // Mention tag with thumbnail + hover preview function MentionTag({ label, thumbUrl, assetType }: { label: string; thumbUrl?: string; assetType?: string }) { const [hover, setHover] = useState(false); + const [thumbBroken, setThumbBroken] = useState(false); const ref = useRef(null); const [pos, setPos] = useState({ top: 0, left: 0 }); const isAudio = assetType === 'Audio' || assetType === 'audio'; + const isVideo = assetType === 'Video' || assetType === 'video'; + const showThumb = thumbUrl && !thumbBroken; return ( <> @@ -49,7 +52,7 @@ function MentionTag({ label, thumbUrl, assetType }: { label: string; thumbUrl?: ref={ref} className={styles.mentionTag} onMouseEnter={() => { - if (!isAudio && thumbUrl && ref.current) { + if (!isAudio && showThumb && ref.current) { const rect = ref.current.getBoundingClientRect(); setPos({ top: rect.top - 8, left: rect.left + rect.width / 2 }); setHover(true); @@ -59,18 +62,30 @@ function MentionTag({ label, thumbUrl, assetType }: { label: string; thumbUrl?: > {isAudio ? ( - ) : thumbUrl ? ( + ) : showThumb ? ( setThumbBroken(true)} /> - ) : null} + ) : isVideo ? ( + + + + + ) : ( + + + + + + )} {label} - {hover && thumbUrl && createPortal( + {hover && showThumb && createPortal(
- {label} + {label} { (e.target as HTMLImageElement).style.display = 'none'; }} />
{label}
, document.body @@ -149,7 +164,7 @@ export function GenerationCard({ task, onOpenDetail }: Props) { const [detailPos, setDetailPos] = useState({ top: 0, right: 0 }); const detailLinkRef = useRef(null); const detailLeaveTimer = useRef | null>(null); - const [refPreview, setRefPreview] = useState<{ url: string; label: string; type: string; top: number; left: number } | null>(null); + const [refPreview, setRefPreview] = useState<{ url: string; label: string; type: string; top: number; left: number; isAssetRef?: boolean } | null>(null); const startDetailLeave = useCallback(() => { if (detailLeaveTimer.current) clearTimeout(detailLeaveTimer.current); @@ -294,11 +309,11 @@ export function GenerationCard({ task, onOpenDetail }: Props) { onMouseEnter={(e) => { if (ref.type === 'audio') return; const rect = e.currentTarget.getBoundingClientRect(); - setRefPreview({ url: ref.previewUrl, label: ref.label, type: ref.type, top: rect.top - 8, left: rect.left + rect.width / 2 }); + setRefPreview({ url: ref.previewUrl, label: ref.label, type: ref.type, top: rect.top - 8, left: rect.left + rect.width / 2, isAssetRef: ref.isAssetRef }); }} onMouseLeave={() => setRefPreview(null)} > - {ref.type === 'video' ? ( + {ref.type === 'video' && !ref.isAssetRef ? (