auto-save 2026-05-17 23:03 (~3)

This commit is contained in:
2026-05-17 23:03:08 +08:00
parent b4b2259440
commit 290a833019
3 changed files with 68 additions and 76 deletions

View File

@@ -826,8 +826,8 @@ export function AdRecreationBoard({
</ActionButton>
</div>
<div className="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-[minmax(0,1fr)_260px]">
<div className="grid grid-cols-4 gap-2 rounded-md border border-white/10 bg-black/28 p-2 text-[11px] text-white/52">
<div className="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-[minmax(0,1fr)_360px]">
<div className="flex min-w-0 flex-wrap items-center gap-1.5 text-[11px] text-white/46">
<Requirement label="素材" ready={!!job} detail={job ? shortId(job.id) : "待输入"} />
<Requirement label="视频" ready={!!job?.video_url} detail={job?.status === "downloading" ? "下载中" : job?.video_url ? "已就绪" : "待下载"} />
<Requirement label="音频" ready={!!job?.source_audio_url} detail={job?.status === "transcribing" ? "解析中" : job?.source_audio_url ? "已提取" : "待提取"} />
@@ -956,41 +956,10 @@ function MaterialColumn({
<EmptyState text="还没有素材。每导入一个链接或上传一个文件,就会新增一个素材任务。" />
)}
</div>
{job?.video_url && (
<video
src={videoUrl(job.id)}
controls
playsInline
className="aspect-video w-full rounded-lg border border-white/10 bg-black object-contain"
/>
)}
</section>
)
}
function AudioIntakeStatus({ job, audioReady }: { job: Job | null; audioReady: boolean }) {
const downloading = !!job && ["created", "downloading"].includes(job.status)
const audioRunning = !!job && (job.status === "transcribing" || job.audio_script?.status === "rewriting")
return (
<div className="rounded-lg border border-white/10 bg-black/32 p-2.5">
<div className="mb-2 flex items-center justify-between gap-2">
<SectionTitle icon={<PanelRight className="h-4 w-4" />} title="当前步骤" />
<StatusPill ready={audioReady} running={downloading || audioRunning} />
</div>
<div className="grid grid-cols-4 gap-2 text-[11px] text-white/52">
<Requirement label="素材" ready={!!job} detail={job ? shortId(job.id) : "待输入"} />
<Requirement label="视频" ready={!!job?.video_url} detail={downloading ? "下载中" : job?.video_url ? "已就绪" : "待下载"} />
<Requirement label="音频" ready={!!job?.source_audio_url} detail={audioRunning ? "解析中" : job?.source_audio_url ? "已提取" : "待提取"} />
<Requirement label="文案" ready={audioReady} detail={audioReady ? `${job?.transcript.length ?? 0}` : "待解析"} />
</div>
<div className="mt-2 truncate rounded-md border border-white/10 bg-black/28 px-3 py-2 text-[11px] text-white/42" title={job?.message}>
{job?.message || "粘贴 TK 链接或上传视频后,点击开始进入下载和音频解析。"}
</div>
</div>
)
}
function AudioIntakePanel({
job,
selectedFrames,
@@ -1132,21 +1101,32 @@ function AudioIntakePanel({
</div>
</div>
<div className="mb-2 grid grid-cols-3 gap-2">
{profiles.map((item) => (
<ProfileTile key={item.label} label={item.label} value={item.value} running={processing} />
))}
</div>
<details className="group mb-2 rounded-md border border-white/10 bg-black/24 p-2">
<summary className="flex cursor-pointer list-none items-center justify-between gap-3">
<SectionTitle icon={<Mic className="h-4 w-4" />} title="音频解析结果" />
<div className="flex min-w-0 items-center gap-2">
<span className="hidden min-w-0 truncate text-[11px] text-white/40 md:inline">
/ /
</span>
<ChevronDown className="h-4 w-4 shrink-0 text-white/38 transition group-open:rotate-180" />
</div>
</summary>
<div className="mt-2 grid grid-cols-1 gap-2 md:grid-cols-3">
{profiles.map((item) => (
<ProfileTile key={item.label} label={item.label} value={item.value} running={processing} />
))}
</div>
</details>
<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-[280px_minmax(0,1fr)] 2xl:grid-cols-[300px_minmax(0,1fr)]">
<div className="grid gap-3 xl:grid-cols-[300px_minmax(0,1fr)] 2xl:grid-cols-[330px_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="mx-auto aspect-[9/16] h-[420px] overflow-hidden rounded-md border border-white/10 bg-black 2xl:h-[480px]">
<div className="relative mx-auto aspect-[9/16] h-[400px] overflow-hidden rounded-md border border-white/10 bg-black 2xl:h-[460px]">
{job.video_url ? (
<video
ref={videoRef}
@@ -1168,17 +1148,17 @@ function AudioIntakePanel({
) : (
<div className="flex h-full items-center justify-center text-[12px] text-white/38"></div>
)}
<button
type="button"
onClick={() => void addFrameAtCurrentTime()}
disabled={!job.video_url || !onAddFrame || manualBusy || job.status === "splitting"}
title={`按当前播放位置手动抽帧:${currentTime.toFixed(1)}s`}
className="absolute right-2 top-2 inline-flex h-7 items-center justify-center gap-1 rounded-md border border-emerald-200/30 bg-black/78 px-2 text-[10.5px] font-semibold text-emerald-100 shadow-lg backdrop-blur transition hover:border-emerald-100/65 hover:bg-emerald-300/18 disabled:cursor-not-allowed disabled:opacity-35"
>
{manualBusy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
</button>
</div>
<button
type="button"
onClick={() => void addFrameAtCurrentTime()}
disabled={!job.video_url || !onAddFrame || manualBusy || job.status === "splitting"}
title={`按当前播放位置手动抽帧:${currentTime.toFixed(1)}s`}
className="mt-2 inline-flex h-8 w-full items-center justify-center gap-1 rounded-md border border-emerald-300/20 bg-emerald-300/[0.08] px-2 text-[11px] font-semibold text-emerald-100 transition hover:border-emerald-200/45 hover:bg-emerald-300/[0.14] disabled:cursor-not-allowed disabled:opacity-35"
>
{manualBusy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
· {currentTime.toFixed(1)}s
</button>
</div>
<div className="min-w-0 space-y-2">
@@ -1415,20 +1395,20 @@ function SourceReferenceBuildPanel({
onClick={() => void extractKeyframes()}
disabled={!job.video_url || extracting || job.status === "splitting"}
title="自动按动作峰值抽 12 张参考帧,更偏向手势、表情变化、节奏点和镜头变化"
className="inline-flex h-7 items-center justify-center gap-1 rounded-md bg-white px-2.5 text-[10.5px] font-semibold text-black transition hover:bg-white/90 disabled:cursor-not-allowed disabled:opacity-40"
className="inline-flex h-8 items-center justify-center gap-1 rounded-md bg-white px-3 text-[11px] font-semibold text-black transition hover:bg-white/90 disabled:cursor-not-allowed disabled:opacity-40"
>
{extracting || job.status === "splitting" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Scissors className="h-3.5 w-3.5" />}
12
</button>
</div>
</div>
<div className="h-[300px] overflow-y-auto rounded-md border border-white/10 bg-black/32 p-2 2xl:h-[340px]">
<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-6 gap-1.5 md:grid-cols-8 xl:grid-cols-12">
<div className="mt-2 grid grid-cols-6 gap-1.5 md:grid-cols-8 xl:grid-cols-12 2xl:grid-cols-16">
{frames.map((frame, index) => {
const selected = selectedFrames.has(frame.index)
return (
@@ -1473,7 +1453,7 @@ function SourceReferenceBuildPanel({
})}
{!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
12
</div>
)}
</div>
@@ -2902,10 +2882,10 @@ function EmptyState({ text }: { text: string }) {
function Requirement({ label, ready, detail }: { label: string; ready: boolean; detail: string }) {
return (
<div className="flex h-10 min-w-0 items-center gap-2 rounded-md border border-white/10 bg-black/28 px-2">
{ready ? <Check className="h-3.5 w-3.5 shrink-0 text-emerald-200" /> : <Circle className="h-3.5 w-3.5 shrink-0 text-white/38" />}
<div className="flex h-7 min-w-0 items-center gap-1.5 rounded-md border border-white/10 bg-black/24 px-2">
{ready ? <Check className="h-3 w-3 shrink-0 text-emerald-200" /> : <Circle className="h-3 w-3 shrink-0 text-white/38" />}
<span className="shrink-0 whitespace-nowrap">{label}</span>
<span className="min-w-0 truncate font-mono text-[11px] text-white/42">{detail}</span>
<span className="min-w-0 truncate font-mono text-[10.5px] text-white/42">{detail}</span>
</div>
)
}