refactor: compact audio intake panel

This commit is contained in:
2026-05-17 14:12:15 +08:00
parent 660348f39d
commit 3030f8938d
3 changed files with 68 additions and 83 deletions

View File

@@ -1,31 +1,5 @@
{
"entries": [
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-14 18:21 (~1)",
"ts": "2026-05-14T10:26:15Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "995dd88",
"message": "auto-save 2026-05-14 18:27 (~1)",
"ts": "2026-05-14T18:27:37+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-14 18:27 (~1)",
"ts": "2026-05-14T10:28:43Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "97c039b",
"message": "auto-save 2026-05-14 22:00 (~1)",
"ts": "2026-05-14T22:33:31+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-14 22:00 (~1)",
@@ -3270,6 +3244,32 @@
"message": "auto-save 2026-05-17 13:56 (~4)",
"hash": "c4b6980",
"files_changed": 4
},
{
"ts": "2026-05-17T13:58:05+08:00",
"type": "commit",
"message": "fix: use local asr for transcript timeline",
"hash": "660348f",
"files_changed": 2
},
{
"ts": "2026-05-17T05:58:25Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交fix: use local asr for transcript timeline",
"files_changed": 1
},
{
"ts": "2026-05-17T06:08:25Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交fix: use local asr for transcript timeline",
"files_changed": 1
},
{
"ts": "2026-05-17T14:12:15+08:00",
"type": "commit",
"message": "auto-save 2026-05-17 14:12 (~3)",
"hash": "c17fd19",
"files_changed": 3
}
]
}

View File

@@ -588,7 +588,7 @@
<tr><td><code>web/next.config.mjs</code></td><td>Next.js 构建配置:静态导出、图片不走优化、禁用开发环境左下角 Next Dev Indicator并移除 Next 16 已不支持的 <code>eslint</code> 顶层配置,避免本地 dev 出现配置 Issue 提示。</td></tr>
<tr><td><code>web/app/globals.css</code></td><td>全局主题变量、登录页视觉样式、ReactFlow 样式引用,以及本地开发态 <code>nextjs-portal</code> 遮挡隐藏规则。</td></tr>
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态jobs、activeJobId、生成任务状态主渲染为全屏素材输入列 + 音频解析工作表;“开始”编排状态只负责在下载完成后自动触发 <code>triggerTranscribe</code>不再默认触发抽帧、Vision 扫描或分镜初稿保存;底部吸附音频条不再从主界面渲染。</td></tr>
<tr><td><code>web/components/ad-recreation-board.tsx</code></td><td>信息流广告音频解析工作表:左侧素材输入;右侧展示视频下载状态、原文案/中文翻译、讲话人/节奏/背景音分析和逐句时间轴。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。</td></tr>
<tr><td><code>web/components/ad-recreation-board.tsx</code></td><td>信息流广告音频解析工作表:左侧素材输入;右侧展示视频下载状态、默认折叠的音频文案依据、逐句时间轴和讲话人/节奏/背景音分析。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。</td></tr>
<tr><td><code>web/app/login/page.tsx</code></td><td>生产登录页:访问账号/访问密钥表单、保持登录、错误/成功状态;当前只在原版 Digital Oasis 动态背景上叠加一个组合登录框,桌面端左侧是动态角色,右侧是图标化登录表单;面板左上角展示官网 SKG 字标和中文“营销内容工作台”系统标识。</td></tr>
<tr><td><code>web/app/login/layout.tsx</code></td><td>登录路由专属 layout覆盖全站默认网页标题和描述为空避免 <code>/login</code> 继承工作台 metadata 后在页面源码里继续出现登录界面文字以外的文案。</td></tr>
<tr><td><code>web/components/login/oasis-canvas.tsx</code></td><td>登录页全屏动态视觉层:用 iframe 直接承载下载包 <code>web/public/oasis-source/index.html</code> 的原 WebGPU / Three.js 草场源码;父级登录页只覆盖自己的文案和表单,并在捕获阶段把全局鼠标坐标同时用原生事件和 <code>postMessage</code> 转发给 iframe避免登录面板或输入框遮挡时草地失去鼠标响应。</td></tr>
@@ -612,7 +612,7 @@
<tr><td><code>api/main.py</code></td><td>FastAPI 单文件后端登录会话、状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、原音频转写/翻译、声音与背景音分析、后续口播改写/TTS、文件返回。</td></tr>
<tr><td><code>api/product_library/skg-products</code></td><td>内置 SKG 白底产品图库:<code>manifest.json</code> 记录从桌面产品图筛出的 gallery 白底图和桌面 4 张产品角度图,<code>images/</code> 存 45 张参考图。</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/state.json</code></td><td>运行时状态文件,不在源码列表里,但刷新恢复依赖它。</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/audio.wav</code></td><td>拆轨得到的原始音频,底部 Audio Strip 会通过只读接口拉取并在浏览器里解码成波形峰值</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/audio.wav</code></td><td>拆轨得到的原始音频,当前只作为后端分析和后续必要预览的只读文件来源;主界面不再默认渲染底部音频条</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/frames</code></td><td>关键帧 jpg。注意 frame.index 是稳定 ID不等于数组下标。</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/cleaned</code></td><td>清洗后待应用图片。</td></tr>
<tr><td><code>jobs/&lt;jobId&gt;/elements</code></td><td>元素提取图,多版本命名:<code>idx_elementId_cutoutId.jpg</code></td></tr>
@@ -625,7 +625,7 @@
web/app/page.tsx
-> 音频解析工作表web/components/ad-recreation-board.tsx
-> 开始:创建/激活 job → 下载完成后自动触发音频处理
-> 左侧素材输入列 + 右侧原文案/中文翻译/声音背景音分析/逐句时间轴
-> 左侧素材输入列 + 右侧默认折叠的音频文案依据 + 逐句时间轴 + 声音背景音分析
-> 底部音频条:不再渲染,音频结果集中到右侧工作表
-> 旧节点/深度素材面板web/components/nodes/index.tsx、web/components/lightbox.tsx、web/components/storyboard-workbench.tsx底层保留当前不作为主入口
-> API 契约web/lib/api.ts
@@ -643,12 +643,12 @@ api/main.py
<div class="flow-row">
<div><strong>你看到的区域</strong><span>信息流广告音频解析工作表</span></div>
<div><strong>主要源码</strong><span><code>AdRecreationBoard</code> in <code>web/components/ad-recreation-board.tsx</code>;状态、轮询和接口回写仍在 <code>web/app/page.tsx</code></span></div>
<div><strong>适合怎么描述</strong><span>“素材输入列、开始后的自动下载/音频解析、原文案/翻译/声音背景音结果怎么展示”。</span></div>
<div><strong>适合怎么描述</strong><span>“素材输入列、开始后的自动下载/音频解析、逐句时间轴和声音背景音结果怎么展示”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>音频解析结果表</span></div>
<div><strong>主要源码</strong><span><code>AudioIntakePanel</code> / <code>AudioIntakeStatus</code> in <code>web/components/ad-recreation-board.tsx</code>;复用 <code>triggerTranscribe</code><code>AudioScript</code></span></div>
<div><strong>适合怎么描述</strong><span>原始文案、中文翻译、讲话人、节奏、背景音、逐句时间轴还需要哪些字段”。</span></div>
<div><strong>适合怎么描述</strong><span>逐句时间轴、讲话人、节奏、背景音还需要哪些字段;全文文案依据是否需要展开查看”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>旧深度素材面板(当前不作为主路径)</span></div>
@@ -863,8 +863,8 @@ SubjectAsset {
</tr>
<tr>
<td><span class="tag gray">音频条</span></td>
<td>音频解析工作表顶部触发音频解析,结果在右侧原文案、中文翻译、逐句时间轴和声音/背景音分析区展示;底部 <code>AudioStrip</code> 当前不渲染。</td>
<td>当前第一步不要默认展示底部音频条、新配音播放器,或把 MiniMax 配音当作已完成结果。</td>
<td>音频解析工作表顶部触发音频解析;全文音频文案依据默认折叠,主展示以逐句时间轴和声音/背景音分析区为准;底部 <code>AudioStrip</code> 当前不渲染。</td>
<td>当前第一步不要默认展示底部音频条、新配音播放器、独立原文案提取大卡片,或把 MiniMax 配音当作已完成结果。</td>
<td><code>web/components/audio-strip.tsx</code><code>pipeline_transcribe</code><code>AudioScript</code></td>
</tr>
<tr>
@@ -941,6 +941,18 @@ SubjectAsset {
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-17 · 收紧音频解析第一步版面</h3>
<span class="tag rose">UI</span>
<span class="tag cyan">Workflow</span>
</header>
<div class="body">
<p><strong>问题:</strong>音频解析第一步顶部“音频文案依据”和主区域“原文案提取”同时展示全文,和逐句时间轴重复,占用太多工作台版面。</p>
<p><strong>改动:</strong><code>web/components/ad-recreation-board.tsx</code> 将顶部“音频文案依据”改为默认折叠的 <code>details</code>;移除主面板里的“原文案提取”大卡片,让“逐句时间轴”成为音频解析后的主展示,声音/背景音分析降到其后。</p>
<p><strong>影响:</strong><code>web/components/ad-recreation-board.tsx</code><code>docs/source-analysis.html</code>。后续如果需要全文原文案,只展开“音频文案依据”,不要再恢复独立原文案大卡片。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-17 · 修复逐句时间轴假字幕</h3>

View File

@@ -2,7 +2,7 @@
import { type ReactNode, type RefObject, useEffect, useRef, useState } from "react"
import {
AlertTriangle, Check, Circle, Film, FileText, Image as ImageIcon, Link2, Loader2,
AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Link2, Loader2,
Mic, Package, PanelRight, Play, Plus, Scissors, Sparkles, Trash2, Upload, Wand2,
} from "lucide-react"
import { toast } from "sonner"
@@ -387,23 +387,20 @@ export function AdRecreationBoard({
</div>
</div>
<div className="mt-3 grid grid-cols-[minmax(0,1fr)_470px] gap-3">
<div className="rounded-lg border border-white/10 bg-black/32 p-3">
<div className="mb-2 flex items-center justify-between">
<div className="mt-3 grid grid-cols-1 items-start gap-3 xl:grid-cols-[minmax(0,1fr)_470px]">
<details className="group rounded-lg border border-white/10 bg-black/32 p-3">
<summary className="flex cursor-pointer list-none items-center justify-between gap-3">
<SectionTitle icon={<FileText className="h-4 w-4" />} title="音频文案依据" />
<StatusPill ready={audioReady} running={job?.status === "transcribing" || job?.audio_script?.status === "rewriting"} />
</div>
<div className="max-h-20 overflow-y-auto rounded-md border border-white/10 bg-black/35 p-2 text-[12px] leading-relaxed text-white/62">
<div className="flex items-center gap-2">
<span className="font-mono text-[11px] text-white/38">{transcriptCount ? `${transcriptCount}` : "待解析"}</span>
<StatusPill ready={audioReady} running={job?.status === "transcribing" || job?.audio_script?.status === "rewriting"} />
<ChevronDown className="h-4 w-4 text-white/38 transition group-open:rotate-180" />
</div>
</summary>
<div className="mt-3 max-h-24 overflow-y-auto rounded-md border border-white/10 bg-black/35 p-2 text-[12px] leading-relaxed text-white/62">
{audioPreview(job)}
</div>
{(job?.audio_script?.speaker_profile || job?.audio_script?.rhythm_profile) && (
<div className="mt-2 grid gap-1 text-[11px] leading-relaxed text-white/42">
{job.audio_script.speaker_profile && <div>{job.audio_script.speaker_profile}</div>}
{job.audio_script.rhythm_profile && <div>{job.audio_script.rhythm_profile}</div>}
{job.audio_script.background_audio_profile && <div>{job.audio_script.background_audio_profile}</div>}
</div>
)}
</div>
</details>
<AudioIntakeStatus job={job} audioReady={audioReady} />
</div>
@@ -548,8 +545,6 @@ function AudioIntakePanel({ job }: { job: Job | null }) {
}
const script = job.audio_script
const original = script?.source_text?.trim() || job.transcript.map((item) => item.en).filter(Boolean).join(" ")
const translated = script?.source_zh?.trim() || job.transcript.map((item) => item.zh).filter(Boolean).join(" ")
const profiles = [
{ label: "讲话人", value: script?.speaker_profile },
{ label: "节奏", value: script?.rhythm_profile },
@@ -559,29 +554,6 @@ function AudioIntakePanel({ job }: { job: Job | null }) {
return (
<div className="grid gap-3">
<section className="rounded-lg border border-white/10 bg-black/28 p-3">
<div className="mb-3 flex items-center justify-between gap-3">
<SectionTitle icon={<FileText className="h-4 w-4" />} title="原文案提取" />
<StatusPill ready={!!original || job.transcript.length > 0} running={processing} />
</div>
<div className="grid gap-3 xl:grid-cols-2">
<TextBlock title="原始文案" value={original} empty={processing ? "正在提取原音频文案..." : "还没有提取到原文案。"} />
<TextBlock title="中文翻译" value={translated} empty={processing ? "正在翻译..." : "还没有中文翻译。"} />
</div>
</section>
<section className="rounded-lg border border-white/10 bg-black/28 p-3">
<div className="mb-3 flex items-center justify-between gap-3">
<SectionTitle icon={<Mic className="h-4 w-4" />} title="声音与背景音分析" />
<span className="font-mono text-[11px] text-white/38">{formatSeconds(job.duration)}</span>
</div>
<div className="grid gap-2 lg:grid-cols-3">
{profiles.map((item) => (
<ProfileTile key={item.label} label={item.label} value={item.value} running={processing} />
))}
</div>
</section>
<section className="rounded-lg border border-white/10 bg-black/28 p-3">
<div className="mb-3 flex items-center justify-between gap-3">
<SectionTitle icon={<Film className="h-4 w-4" />} title="逐句时间轴" />
@@ -608,17 +580,18 @@ function AudioIntakePanel({ job }: { job: Job | null }) {
<EmptyState text={processing ? "音频解析中,完成后这里会按时间列出原文案和中文翻译。" : "下载完成后会自动解析音频;也可以点击右上角“解析音频”手动重试。"} />
)}
</section>
</div>
)
}
function TextBlock({ title, value, empty }: { title: string; value?: string; empty: string }) {
return (
<div className="min-h-[156px] rounded-lg border border-white/10 bg-black/35 p-3">
<div className="mb-2 text-[11px] font-semibold text-white/48">{title}</div>
<div className="max-h-[220px] overflow-y-auto whitespace-pre-wrap text-[12.5px] leading-relaxed text-white/72">
{value || <span className="text-white/32">{empty}</span>}
</div>
<section className="rounded-lg border border-white/10 bg-black/28 p-3">
<div className="mb-3 flex items-center justify-between gap-3">
<SectionTitle icon={<Mic className="h-4 w-4" />} title="声音与背景音分析" />
<span className="font-mono text-[11px] text-white/38">{formatSeconds(job.duration)}</span>
</div>
<div className="grid gap-2 lg:grid-cols-3">
{profiles.map((item) => (
<ProfileTile key={item.label} label={item.label} value={item.value} running={processing} />
))}
</div>
</section>
</div>
)
}