auto-save 2026-05-17 23:03 (~3)
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user