diff --git a/src/components/PackPanel.tsx b/src/components/PackPanel.tsx index 35bfa85..dfcf4ca 100644 --- a/src/components/PackPanel.tsx +++ b/src/components/PackPanel.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; -import type { AssetPack, AssetTemplate, GenImage, GenSession, PackKind, ToyAsset } from '@/lib/types'; +import type { AssetPack, AssetTemplate, GenImage, GenSession, PackKind, ToyAsset, VideoTask } from '@/lib/types'; import { PACK_LABELS, PACK_ORDER, PACK_TEMPLATES, TEXT_TEMPLATES, VIDEO_TEMPLATES } from '@/lib/templates'; import { HoverImagePreview, HoverVideoPreview } from './HoverImagePreview'; @@ -35,6 +35,30 @@ function aspectCss(aspectRatio: AssetTemplate['aspectRatio'] | string | undefine return aspectRatio.replace(':', ' / '); } +function canonicalVideoTemplateId(templateId: string) { + return VIDEO_TEMPLATES.find(template => ( + templateId === template.id || templateId.startsWith(`${template.id}_`) + ))?.id ?? templateId; +} + +function videoTaskScore(task: VideoTask) { + let score = 0; + if (task.videoUrl) score += 8; + if (task.status === 'succeeded') score += 4; + if (task.status === 'processing' || task.status === 'submitted') score += 2; + return score; +} + +function selectVideoTaskForTemplate(tasks: VideoTask[], templateId: string) { + return tasks + .filter(task => canonicalVideoTemplateId(task.templateId) === templateId) + .sort((a, b) => { + const scoreDiff = videoTaskScore(b) - videoTaskScore(a); + if (scoreDiff) return scoreDiff; + return (b.updatedAt || b.submittedAt || 0) - (a.updatedAt || a.submittedAt || 0); + })[0]; +} + type AssetDetail = { template: AssetTemplate; asset: ToyAsset | undefined; @@ -379,20 +403,23 @@ function VideoSection({ videoLoading, primaryImage, locked, session, onGenerateV onRefreshVideo: (taskId: string) => void; }) { const [showPromptId, setShowPromptId] = useState(null); - const videoTasks = session.videoTasks ?? []; - const byTemplate = new Map(videoTasks.map(task => [task.templateId, task])); + const videoTasks = (session.videoTasks ?? []).filter(task => !/_part[12]$/.test(task.templateId)); const builtInIds = new Set(VIDEO_TEMPLATES.map(template => template.id)); - const extraTasks = videoTasks.filter(task => !builtInIds.has(task.templateId) && !/_part[12]$/.test(task.templateId)); + const extraTasks = videoTasks.filter(task => !builtInIds.has(canonicalVideoTemplateId(task.templateId))); const videoItems = [ - ...VIDEO_TEMPLATES.map(template => ({ - id: template.id, - title: template.title, - description: template.description, - duration: template.duration, - ratio: template.ratio, - promptTemplate: template.promptTemplate, - template, - })), + ...VIDEO_TEMPLATES.map(template => { + const task = selectVideoTaskForTemplate(videoTasks, template.id); + return { + id: template.id, + title: task?.title ?? template.title, + description: task?.description ?? template.description, + duration: task?.duration ?? template.duration, + ratio: task?.ratio ?? template.ratio, + promptTemplate: task?.prompt ?? template.promptTemplate, + template, + task, + }; + }), ...extraTasks.map(task => ({ id: task.templateId, title: task.title, @@ -401,9 +428,10 @@ function VideoSection({ videoLoading, primaryImage, locked, session, onGenerateV ratio: task.ratio, promptTemplate: task.prompt, template: null, + task, })), ]; - const submittedCount = videoItems.filter(item => byTemplate.has(item.id)).length; + const submittedCount = videoItems.filter(item => item.task).length; const totalCount = Math.max(videoItems.length, 1); return ( @@ -430,8 +458,8 @@ function VideoSection({ videoLoading, primaryImage, locked, session, onGenerateV
{videoItems.map(item => { const isOpen = showPromptId === item.id; - const task = byTemplate.get(item.id); - const loadingThis = videoLoading === item.id; + const task = item.task; + const loadingThis = videoLoading === item.id || videoLoading === task?.templateId; return (