feat: refine source video keyframe layout

This commit is contained in:
2026-05-18 14:51:44 +08:00
parent 665a0efca6
commit 4991526bcc
2 changed files with 87 additions and 36 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1837,13 +1837,13 @@ function AudioIntakePanel({
<div className="grid gap-2 border-t border-white/8 pt-2">
<div className="grid gap-2">
<div className="grid gap-3 xl:grid-cols-[300px_minmax(0,1fr)] 2xl:grid-cols-[330px_minmax(0,1fr)]">
<div className="grid gap-3 xl:grid-cols-[360px_minmax(0,1fr)] 2xl:grid-cols-[390px_minmax(0,1fr)]">
<div className="min-w-0">
<div className="mb-2 flex items-center justify-between gap-3">
<SectionTitle icon={<Play className="h-4 w-4" />} title="原版视频" />
<span className="font-mono text-[11px] text-white/38">{currentTime.toFixed(1)}s</span>
</div>
<div className="relative mx-auto aspect-[9/16] h-[400px] overflow-hidden rounded-md border border-white/10 bg-black 2xl:h-[460px]">
<div className="relative mx-auto aspect-[9/16] h-[450px] overflow-hidden rounded-md border border-white/10 bg-black 2xl:h-[510px]">
{job.video_url ? (
<video
ref={videoRef}
@@ -2168,39 +2168,78 @@ function SourceReferenceBuildPanel({
</div>
</div>
<div className="h-[250px] overflow-y-auto rounded-md border border-white/10 bg-black/32 p-2 2xl:h-[290px]">
<div className="flex items-center justify-between gap-2">
<span className="text-[10.5px] text-white/34"></span>
<span className="text-[10.5px] text-white/30"></span>
</div>
<div className="mt-2 grid grid-cols-[repeat(auto-fill,minmax(38px,1fr))] gap-1">
{frames.map((frame, index) => {
const selected = selectedFrames.has(frame.index)
return (
<MediaAssetTile
key={frame.index}
src={effectiveFrameUrl(job.id, frame)}
alt={`关键帧 ${index + 1}`}
label={`参考帧 ${String(index + 1).padStart(2, "0")}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="aspect-[9/16]"
objectFit="contain"
selected={selected}
title={`关键帧 ${index + 1} · ${frame.timestamp.toFixed(1)}s`}
onClick={() => onToggleFrame(frame.index)}
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
topRight={<span className="rounded-full bg-black/72 p-0.5">{selected ? <Check className="h-3 w-3 text-emerald-200" /> : <Circle className="h-3 w-3 text-white/50" />}</span>}
onDelete={onDeleteFrame ? () => void deleteReferenceFrame(frame.index) : undefined}
deleting={deletingFrame === frame.index}
deleteLabel={`删除关键帧 ${index + 1}`}
/>
)
})}
{!frames.length && (
<div className="col-span-full flex h-[106px] items-center justify-center rounded border border-dashed border-white/12 text-[11px] text-white/34">
12
<div className="grid gap-2 xl:grid-cols-[minmax(0,1fr)_138px] 2xl:grid-cols-[minmax(0,1fr)_158px]">
<div className="min-w-0">
<div className="flex items-center justify-between gap-2">
<span className="text-[10.5px] text-white/34"></span>
<span className="text-[10.5px] text-white/30"></span>
</div>
)}
<div className="mt-2 grid grid-cols-[repeat(auto-fill,minmax(38px,1fr))] gap-1">
{frames.map((frame, index) => {
const selected = selectedFrames.has(frame.index)
return (
<MediaAssetTile
key={frame.index}
src={effectiveFrameUrl(job.id, frame)}
alt={`关键帧 ${index + 1}`}
label={`参考帧 ${String(index + 1).padStart(2, "0")}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="aspect-[9/16]"
objectFit="contain"
selected={selected}
title={`关键帧 ${index + 1} · ${frame.timestamp.toFixed(1)}s`}
onClick={() => onToggleFrame(frame.index)}
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
topRight={<span className="rounded-full bg-black/72 p-0.5">{selected ? <Check className="h-3 w-3 text-emerald-200" /> : <Circle className="h-3 w-3 text-white/50" />}</span>}
onDelete={onDeleteFrame ? () => void deleteReferenceFrame(frame.index) : undefined}
deleting={deletingFrame === frame.index}
deleteLabel={`删除关键帧 ${index + 1}`}
/>
)
})}
{!frames.length && (
<div className="col-span-full flex h-[106px] items-center justify-center rounded border border-dashed border-white/12 text-[11px] text-white/34">
12
</div>
)}
</div>
</div>
<aside className="min-w-0 rounded-md border border-emerald-300/12 bg-emerald-300/[0.035] p-1.5">
<div className="mb-1 flex items-center justify-between gap-1">
<span className="text-[10px] font-semibold text-emerald-50/70"></span>
<span className="rounded border border-emerald-300/15 bg-black/24 px-1.5 py-0.5 font-mono text-[9px] text-emerald-50/55">{selectedReferenceFrames.length}</span>
</div>
{selectedReferenceFrames.length ? (
<div className="grid max-h-[176px] grid-cols-3 gap-1 overflow-y-auto pr-0.5 2xl:max-h-[210px]">
{selectedReferenceFrames.map((frame) => {
const order = frames.findIndex((item) => item.index === frame.index)
const label = order >= 0 ? String(order + 1).padStart(2, "0") : String(frame.index)
return (
<MediaAssetTile
key={frame.index}
src={effectiveFrameUrl(job.id, frame)}
alt={`已选关键帧 ${label}`}
label={`已选 ${label}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="aspect-[9/16]"
objectFit="contain"
selected
title={`点击取消选择 · ${frame.timestamp.toFixed(1)}s`}
onClick={() => onToggleFrame(frame.index)}
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[8.5px] text-emerald-100/80">{label}</span>}
topRight={<span className="rounded-full bg-black/72 p-0.5"><Check className="h-2.5 w-2.5 text-emerald-200" /></span>}
/>
)
})}
</div>
) : (
<div className="flex h-[106px] items-center justify-center rounded border border-dashed border-emerald-300/12 px-2 text-center text-[10px] leading-snug text-white/30">
</div>
)}
</aside>
</div>
<div className="mt-2 border-t border-white/8 pt-2">