Files
20260512-skg-tk/docs/source-analysis.html
2026-05-15 19:19:18 +08:00

2210 lines
170 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SKG TK 二创验证 · 源码解析与协作地图</title>
<style>
:root {
--bg: #f8fafc;
--panel: #ffffff;
--panel-soft: #f1f5f9;
--ink: #0f172a;
--muted: #64748b;
--line: #dbe3ed;
--blue: #2563eb;
--violet: #7c3aed;
--orange: #ea580c;
--green: #059669;
--rose: #e11d48;
--code: #111827;
--shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
color: var(--ink);
background:
linear-gradient(180deg, rgba(37, 99, 235, 0.06), transparent 340px),
var(--bg);
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
a { color: inherit; }
code, pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
}
.shell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100vh;
}
aside {
position: sticky;
top: 0;
height: 100vh;
overflow: auto;
padding: 24px 18px;
border-right: 1px solid var(--line);
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(16px);
}
main {
width: min(1180px, calc(100vw - 320px));
padding: 34px 40px 72px;
}
.brand {
margin-bottom: 18px;
padding-bottom: 18px;
border-bottom: 1px solid var(--line);
}
.brand h1 {
margin: 0 0 8px;
font-size: 18px;
line-height: 1.25;
letter-spacing: 0;
}
.brand p {
margin: 0;
color: var(--muted);
font-size: 13px;
}
nav a {
display: block;
padding: 8px 10px;
margin: 2px 0;
border-radius: 8px;
color: #334155;
text-decoration: none;
font-size: 13px;
}
nav a:hover,
nav a:focus {
color: var(--blue);
background: #eff6ff;
outline: none;
}
.toolbar {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
margin-top: 18px;
}
.search {
width: 100%;
min-height: 42px;
padding: 0 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: #fff;
color: var(--ink);
font-size: 14px;
outline: none;
}
.search:focus {
border-color: rgba(37, 99, 235, 0.55);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 42px;
padding: 0 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: #fff;
color: #334155;
font-size: 13px;
cursor: pointer;
}
.button:hover { border-color: #94a3b8; color: var(--ink); }
.hero {
padding: 28px;
border: 1px solid var(--line);
border-radius: 18px;
background: var(--panel);
box-shadow: var(--shadow);
}
.hero .eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
color: var(--blue);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero h2 {
margin: 0 0 12px;
max-width: 980px;
font-size: clamp(30px, 5vw, 54px);
line-height: 1.04;
letter-spacing: 0;
}
.hero p {
max-width: 880px;
margin: 0;
color: #475569;
font-size: 16px;
}
.meta-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-top: 18px;
}
.meta {
padding: 13px;
border: 1px solid var(--line);
border-radius: 12px;
background: #f8fafc;
}
.meta b {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: #334155;
}
.meta span {
color: var(--muted);
font-size: 12px;
word-break: break-word;
}
section {
margin-top: 28px;
padding: 24px;
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 255, 255, 0.86);
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.045);
}
section h2 {
margin: 0 0 12px;
font-size: 24px;
letter-spacing: 0;
}
section > p {
margin-top: 0;
color: #475569;
}
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.card {
padding: 16px;
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
}
.card h3 {
margin: 0 0 8px;
font-size: 16px;
}
.card p,
.card li {
color: #475569;
font-size: 14px;
}
.card p:last-child { margin-bottom: 0; }
.tag {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 8px;
border-radius: 999px;
background: #e2e8f0;
color: #334155;
font-size: 12px;
font-weight: 650;
}
.tag.blue { background: #dbeafe; color: #1d4ed8; }
.tag.violet { background: #ede9fe; color: #6d28d9; }
.tag.orange { background: #ffedd5; color: #c2410c; }
.tag.green { background: #d1fae5; color: #047857; }
.tag.rose { background: #ffe4e6; color: #be123c; }
.tag.gray { background: #e2e8f0; color: #475569; }
.pipeline {
display: grid;
grid-template-columns: repeat(8, minmax(120px, 1fr));
gap: 10px;
overflow-x: auto;
padding-bottom: 4px;
}
.step {
min-width: 138px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
}
.step .num {
width: 26px;
height: 26px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
border-radius: 8px;
background: #eff6ff;
color: var(--blue);
font-weight: 800;
font-size: 12px;
}
.step h3 {
margin: 0 0 6px;
font-size: 14px;
}
.step p {
margin: 0;
color: #64748b;
font-size: 12px;
line-height: 1.45;
}
table {
width: 100%;
border-collapse: collapse;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 12px;
background: #fff;
}
th,
td {
padding: 11px 12px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
font-size: 13px;
}
th {
background: #f8fafc;
color: #334155;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
tr:last-child td { border-bottom: 0; }
td code {
color: #0f172a;
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 2px 5px;
font-size: 12px;
}
pre {
margin: 12px 0 0;
padding: 14px;
overflow: auto;
border-radius: 12px;
background: var(--code);
color: #e5e7eb;
font-size: 12px;
line-height: 1.55;
}
.callout {
padding: 14px 16px;
border-radius: 14px;
border: 1px solid #bfdbfe;
background: #eff6ff;
color: #1e3a8a;
}
.callout.warn {
border-color: #fed7aa;
background: #fff7ed;
color: #9a3412;
}
.callout.good {
border-color: #bbf7d0;
background: #f0fdf4;
color: #166534;
}
.callout p { margin: 0; }
.flow {
display: grid;
gap: 10px;
}
.flow-row {
display: grid;
grid-template-columns: 170px minmax(0, 1fr) minmax(0, 1fr);
gap: 10px;
align-items: stretch;
}
.flow-row > div {
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: #fff;
font-size: 13px;
}
.flow-row strong { display: block; margin-bottom: 4px; }
.flow-row span { color: var(--muted); }
.todo {
display: grid;
gap: 10px;
counter-reset: todo;
}
.todo-item {
position: relative;
padding: 14px 14px 14px 46px;
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
}
.todo-item::before {
counter-increment: todo;
content: counter(todo);
position: absolute;
left: 14px;
top: 14px;
width: 22px;
height: 22px;
display: grid;
place-items: center;
border-radius: 7px;
background: #f1f5f9;
color: #475569;
font-size: 12px;
font-weight: 800;
}
.todo-item h3 { margin: 0 0 4px; font-size: 14px; }
.todo-item p { margin: 0; color: var(--muted); font-size: 13px; }
.changelog {
display: grid;
gap: 12px;
}
.change {
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
overflow: hidden;
}
.change header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 12px 14px;
border-bottom: 1px solid var(--line);
background: #f8fafc;
}
.change h3 { margin: 0; font-size: 14px; }
.change .body { padding: 14px; }
.change .body p { margin: 0 0 8px; color: #475569; font-size: 13px; }
.change .body p:last-child { margin-bottom: 0; }
.hidden-by-search { display: none !important; }
@media (max-width: 980px) {
.shell { display: block; }
aside {
position: static;
height: auto;
border-right: 0;
border-bottom: 1px solid var(--line);
}
main {
width: 100%;
padding: 24px 18px 56px;
}
.meta-grid,
.grid-2,
.grid-3,
.grid-4,
.flow-row {
grid-template-columns: 1fr;
}
}
@media print {
aside, .toolbar { display: none; }
.shell { display: block; }
main { width: 100%; padding: 0; }
section, .hero { box-shadow: none; break-inside: avoid; }
body { background: #fff; }
}
</style>
</head>
<body>
<div class="shell">
<aside>
<div class="brand">
<h1>SKG TK 二创验证</h1>
<p>源码解析与协作地图。用于把产品需求准确映射到代码位置。</p>
</div>
<nav aria-label="页面目录">
<a href="#purpose">为什么有这页</a>
<a href="#how-to-use">怎么用它描述需求</a>
<a href="#runtime">运行与入口</a>
<a href="#pipeline">业务管线</a>
<a href="#source-map">源码结构地图</a>
<a href="#ui-map">界面区域到源码</a>
<a href="#data-model">数据模型</a>
<a href="#api-map">接口地图</a>
<a href="#node-contract">节点职责边界</a>
<a href="#current-state">当前已通与阻塞</a>
<a href="#request-language">需求描述模板</a>
<a href="#change-log">变更记录</a>
<a href="#update-rule">更新规则</a>
</nav>
<div class="toolbar">
<input id="search" class="search" type="search" placeholder="搜索:节点 / 文件 / 接口 / 功能" />
<button class="button" type="button" onclick="window.print()">打印</button>
</div>
</aside>
<main>
<div class="hero" id="purpose" data-search>
<div class="eyebrow">Source Analysis · 2026-05-13</div>
<h2>这个页面是产品协作地图,不是应用功能页。</h2>
<p>
它把“你看到的界面、你想改的功能、实际要动的源码、可能影响的数据和接口”放在同一个地方。
后续描述需求时,可以直接说“改源码地图里的某个区域 / 某个节点职责 / 某个接口行为”,这样改动范围会更准,也更容易追踪每次变更带来的影响。
</p>
<div class="meta-grid">
<div class="meta"><b>项目路径</b><span>/Users/kangwan/Projects/business/20260512-20260512-skg-tk-二创验证</span></div>
<div class="meta"><b>前端</b><span>Next.js 16 · 端口 4290 · web/app/page.tsx</span></div>
<div class="meta"><b>后端</b><span>FastAPI · 端口 4291 · api/main.py</span></div>
<div class="meta"><b>本文档位置</b><span>docs/source-analysis.html · 独立文件,不接入应用</span></div>
</div>
</div>
<section id="how-to-use" data-search>
<h2>怎么用它描述需求</h2>
<div class="grid-3">
<div class="card">
<h3>1. 先说你在改哪个产品区</h3>
<p>例如“镜头拆解 / 元素提取面板”、“元素改造 Storyboard 节点”、“分镜头编排下拉区 4 图槽”。不要只说“这里乱”,要指向页面里的功能区。</p>
</div>
<div class="card">
<h3>2. 再说这个区应该承担什么职责</h3>
<p>例如“Vision 只给候选元素,用户必须能编辑、删除、重新提取”,这会直接落到 <code>FrameLightbox</code> 和元素接口。</p>
</div>
<div class="card">
<h3>3. 最后说不希望发生什么</h3>
<p>例如“点击元素不要跳页面”、“不要直接进入编排打断思路”、“不要把参考视频复刻成一样的东西”。这能约束交互和文案。</p>
</div>
</div>
<div class="callout good" style="margin-top:14px">
<p>建议表达格式:我要改「功能区」;当前问题是「行为」;正确职责是「业务目的」;不要影响「已有流程」。</p>
</div>
</section>
<section id="runtime" data-search>
<h2>运行与入口</h2>
<table>
<thead>
<tr><th>项目</th><th>命令 / 入口</th><th>说明</th></tr>
</thead>
<tbody>
<tr>
<td>本地后台启动</td>
<td><code>./scripts/start-dev-background.sh</code></td>
<td>不弹出 macOS Terminal 窗口;自动检查 4290 / 4291缺哪个启动哪个日志写入 <code>.logs/</code>PID 写入 <code>.pids/</code></td>
</tr>
<tr>
<td>本地后台停止</td>
<td><code>./scripts/stop-dev-background.sh</code></td>
<td><code>.pids/</code> 里的 PID 停止后台前端 / 后端进程。</td>
</tr>
<tr>
<td>生产站点</td>
<td><code>https://marketing.skg.com</code></td>
<td>公司域名已解析到 VPS <code>76.13.31.179</code>。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 <code>web</code> 容器用 Nginx 承载静态前端;<code>/login/</code><code>/_next/</code><code>/assets/</code><code>/oasis-source/</code> 为公开登录页资源,未登录访问工作台跳转 <code>/login/</code><code>/api/</code> 通过 <code>auth_request</code> 校验 FastAPI 会话 Cookie 后再反代。</td>
</tr>
<tr>
<td>生产部署</td>
<td><code>docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build</code></td>
<td>服务器目录为 <code>/opt/skg-marketing-studio</code>;后端任务文件挂载到 <code>./data/jobs</code>,真实 Key 只放服务器 <code>deploy/.env.production</code></td>
</tr>
<tr>
<td>前端开发服务</td>
<td><code>cd web && pnpm dev</code></td>
<td>Next.js App Router主页面是 <code>web/app/page.tsx</code>,默认端口 4290。</td>
</tr>
<tr>
<td>后端开发服务</td>
<td><code>cd api && source .venv/bin/activate && uvicorn main:app --host 127.0.0.1 --port 4291</code></td>
<td>FastAPI所有任务状态、视频、关键帧、清洗、元素、分镜保存都在 <code>api/main.py</code>。长下载 / 抽帧 / 音频处理期间不要带 <code>--reload</code>,否则 reload 会等待后台任务结束并让新请求卡住。</td>
</tr>
<tr>
<td>测试页面</td>
<td><code>http://localhost:4290/?job=c6767f3a166b</code></td>
<td>URL 里可放多个 job id<code>?job=id1,id2,id3</code>,前端会恢复多个任务并激活最后一个。</td>
</tr>
<tr>
<td>源码解析页</td>
<td><code>open docs/source-analysis.html</code></td>
<td>独立静态 HTML不被 Next 构建、不影响产品工作台。</td>
</tr>
</tbody>
</table>
</section>
<section id="pipeline" data-search>
<h2>业务管线</h2>
<p>当前产品不是“复制别人的视频”,而是拆解参考视频,提取可借鉴的镜头元素,再改造成 SKG 产品语境的视频素材。</p>
<div class="pipeline">
<div class="step"><div class="num">1</div><h3>输入</h3><p>TK 链接或本地上传,后端下载/保存源视频。</p></div>
<div class="step"><div class="num">2</div><h3>镜头拆解</h3><p>拆轨、抽关键帧、手动加帧,形成参考分镜池。当前主题默认直接抽 12 帧,并使用“透明骨架人”抽帧目标:抽帧阶段只走本机算力扫描、评分、去重和时间覆盖;透明骨架人的语义判断放到后续审核/识别,不在抽帧阶段逐帧调用 Vision。</p></div>
<div class="step"><div class="num">3</div><h3>清洗水印</h3><p>对关键帧做全图或区域清洗,清洗版先进入待审核状态;确认后可单张替换,也可一键替换全部待应用清洗版。</p></div>
<div class="step"><div class="num">4</div><h3>主体识别</h3><p>识别场景和主体候选,只是候选,不应锁死。</p></div>
<div class="step"><div class="num">5</div><h3>素材准备</h3><p>清洗关键帧,把多张关键帧作为同一主体的参考,先重绘六张标准站立主体资产图,再按关键帧生成多个去主体、相似或换风格场景图。</p></div>
<div class="step"><div class="num">6</div><h3>分镜改造</h3><p>把内置透明骨架人角色、场景/镜头描述和固定 SKG 产品放入分镜结构;产品融合使用纵向 6 行镜头工作表,只选角色、微调描述词和秒数。</p></div>
<div class="step"><div class="num">7</div><h3>生成视频</h3><p>普通分镜可调用 Seedance / Kling / Veo 3产品融合自动传入所选角色 7 张参考图和固定 4 张 SKG 产品图,用 Seedance 按秒数生成视频,结果回写到对应行。</p></div>
<div class="step"><div class="num">8</div><h3>声音文案</h3><p>音频轨独立处理:提取原音频并按实际秒数生成 SKG 英文产品介绍 voice-overASR/翻译只作为改前对照和节奏参考;配置 MiniMax 后从男声、女声、成熟声池随机生成自然英文配音 mp3。底部音频条播放原音频时指针会按时间走过字幕节点。</p></div>
<div class="step"><div class="num">9</div><h3>合成成品</h3><p>片段、字幕、配音、转场合成最终 mp4。当前未实现。</p></div>
</div>
</section>
<section id="source-map" data-search>
<h2>源码结构地图</h2>
<div class="grid-2">
<div class="card">
<h3>前端核心</h3>
<table>
<tbody>
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态jobs、activeJobId、按 job 隔离的 selectedFrames/详情面板状态、clipboard、ReactFlow 节点和边;负责打开/找回画布工作面板。</td></tr>
<tr><td><code>web/app/login/page.tsx</code></td><td>生产登录页:访问账号/访问密钥表单、保持会话、错误/成功状态;当前只在原版 Digital Oasis 动态背景上叠加登录表单和动态角色,不再保留 SKG 标题、内容创作中枢、Pipeline、指标栏等额外文案。</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 草场源码;父级登录页只覆盖自己的文案和表单,并在捕获阶段把全局鼠标坐标转发给 iframe避免登录面板遮挡时草地失去鼠标响应。</td></tr>
<tr><td><code>web/public/oasis-source/index.html</code></td><td>从下载包 <code>remix-3d-website-the-digital-o</code> 复制来的原始视觉源码。只额外隐藏 demo 站自己的导航、文字和设置面板,保留原 WebGPU 渲染、草场、景深和鼠标交互源码;同时接收父页面 <code>postMessage</code> 指针坐标驱动草地交互。</td></tr>
<tr><td><code>web/components/login/animated-login-characters.tsx</code></td><td>登录页四个几何动态角色组件:当前作为原版 Oasis 背景上的独立透明框架展示,保留鼠标眼神跟随、输入、显示密码、错误和成功状态反馈。</td></tr>
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义Input、VisualLab、Audio、Compose以及画布工作面板 KeyframePanel / VideoFramePanel旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布。</td></tr>
<tr><td><code>web/components/audio-strip.tsx</code></td><td>底部吸附音频条:可拖拽调整高度;播放原音频时移动指针,逐个高亮英文/中文字幕节点和对应波形,并在右侧固定显示按原音频时长生成的 SKG 英文产品口播和 MiniMax 随机英文配音。</td></tr>
<tr><td><code>web/components/lightbox.tsx</code></td><td>关键帧素材准备面板:清洗、统一主体候选、参考帧网格、六张主体重绘图、每帧去主体场景图、纵向 6 行产品融合镜头工作表和审核。</td></tr>
<tr><td><code>web/components/product-library-picker.tsx</code></td><td>SKG 内置白底产品图库选择器:搜索、品类筛选、预览尺寸,并把库内图片复制为当前 job 的 <code>asset</code></td></tr>
<tr><td><code>web/components/storyboard-bar.tsx</code></td><td>顶部分镜编排条:展示选入编排的关键帧,并作为唯一分镜导航。</td></tr>
<tr><td><code>web/components/storyboard-workbench.tsx</code></td><td>顶部分镜编排条下方的明细区4 图槽、改造目标、时长、自动保存。</td></tr>
<tr><td><code>web/lib/api.ts</code></td><td>前端类型和 API client是前后端数据契约镜像。</td></tr>
</tbody>
</table>
</div>
<div class="card">
<h3>后端核心</h3>
<table>
<tbody>
<tr><td><code>api/main.py</code></td><td>FastAPI 单文件后端登录会话、状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、音频文案改写、MiniMax 英文配音、文件返回。</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;/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>
<tr><td><code>jobs/&lt;jobId&gt;/gen</code></td><td>关键帧生图结果。</td></tr>
</tbody>
</table>
</div>
</div>
<pre>前端主链路:
web/app/page.tsx
-> ReactFlow 节点web/components/nodes/index.tsx
-> 主画布Input → VisualLab / Audio → Compose
-> 底部音频条web/components/audio-strip.tsx原音频播放 / 指针 / 英文 / 中文 / 波形 / 英文改写稿)
-> 画布内视频抽帧面板InputNode 单击视频缩略图打开 videoFramePanel
-> 画布内镜头拆解面板VisualLabNode 打开 keyframePanel内嵌 web/components/lightbox.tsx
-> 分镜工作台web/components/storyboard-workbench.tsx底层保留
-> API 契约web/lib/api.ts
后端主链路:
api/main.py
-> Job / KeyFrame / KeyElement / StoryboardScene / AudioScript
-> 下载 / 上传 / 抽帧 / Vision / 清洗 / 元素提取 / 分镜保存 / 音频文案改写 / MiniMax 英文配音
-> jobs/&lt;jobId&gt;/state.json + 图片文件落盘</pre>
</section>
<section id="ui-map" data-search>
<h2>界面区域到源码</h2>
<div class="flow">
<div class="flow-row">
<div><strong>你看到的区域</strong><span>Input 节点和视频缩略图</span></div>
<div><strong>主要源码</strong><span><code>InputNode</code> in <code>web/components/nodes/index.tsx</code>;状态处理在 <code>page.tsx</code></span></div>
<div><strong>适合怎么描述</strong><span>“输入/下载/视频就绪这个节点应该如何提示用户下一步”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>画面工作台 · Visual Lab</span></div>
<div><strong>主要源码</strong><span><code>VisualLabNode</code> in <code>web/components/nodes/index.tsx</code>;它现在是素材准备看板,汇总关键帧、主体资产包、场景图和视频任务。</span></div>
<div><strong>适合怎么描述</strong><span>“画面工作台的素材准备进度、分组缩略图、关键帧审核入口和后续分镜入口应该如何组织”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>关键帧素材审核面板</span></div>
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、首尾帧、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,首尾帧页左侧显示全部关键帧并可勾选人物/机位参考。主体识别页会显示透明骨架人目标和 Vision 验收分数。清洗页右侧支持一键清洗未处理帧、单张替换清洗版和一键替换全部待应用清洗版;批量替换顺序调用 <code>applyCleanedFrame</code>,不新增后端接口。产品融合页左侧是纵向 6 行镜头工作表:顶部选择 5 个内置透明骨架人角色之一,并常驻显示桌面四张真实 SKG 产品角度图;每行只显示已预填场景/产品使用/享受描述、秒数、生成按钮和可横向追加的视频历史结果,产品图和结果视频都支持鼠标停留放大预览。描述词内置 36 条镜头语言模板,按“建立出场、产品入画、佩戴贴合、使用感受、生活延展、收尾记忆”排列,并且会按角色自动改写场景气质、使用动作和享受状态。每行还内置角色参考图调度:例如正面/半身用于出场,侧面/背部特写用于佩戴贴合,半身/背部特写用于收尾产品记忆点。点击“换一组”只刷新 6 行描述词。四张桌面 SKG 产品图是真实产品真源,所选角色 7 张参考图是人物身份参考,生成时分别通过 <code>copyProductLibraryAsset</code><code>copyCharacterLibraryAssets</code> 自动写入当前 job视频 prompt 要求产品作为外置刚性实物合成到后颈外侧,禁止穿模、融进透明身体或重绘产品。不再暴露产品角度槽、产品融合辅助栏、产品图库选择器或首尾帧槽。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立透明骨架人资产图;首尾帧页保留给旧流程/单独生图,不再是产品融合必填步骤。相关接口包括 <code>cleanupFrame</code><code>applyCleanedFrame</code><code>addElement</code><code>generateSubjectAssets</code><code>generateSceneAsset</code><code>copyProductLibraryAsset</code><code>copyCharacterLibraryAssets</code></span></div>
<div><strong>适合怎么描述</strong><span>“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图、产品融合镜头组和质量风险应该如何审核”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>顶部分镜头编排下拉面板</span></div>
<div><strong>主要源码</strong><span><code>StoryboardWorkbench</code>;保存到 <code>frame.storyboard</code>;接口 <code>PUT /storyboard</code>。SKG 产品参考区同时支持上传、剪贴板和内置白底产品库。</span></div>
<div><strong>适合怎么描述</strong><span>“每个分镜需要哪些图片槽、哪些改造说明,哪些 SKG 产品图要作为视频生成参考”。</span></div>
</div>
</div>
</section>
<section id="data-model" data-search>
<h2>数据模型</h2>
<div class="grid-2">
<div class="card">
<h3>Job</h3>
<p>一个视频任务。前端维护多个 <code>jobs[]</code>,当前激活的是 <code>activeJobId</code>。URL 查询参数会持久化多个 job。</p>
<pre>Job {
id, url, status, progress, message,
video_url, source_audio_url, duration, width, height,
frames: KeyFrame[],
transcript: TranscriptSegment[],
audio_script: AudioScript,
storyboard_images?: StoryboardImage[]
}</pre>
</div>
<div class="card">
<h3>KeyFrame</h3>
<p>关键帧是整个产品的核心单位。<code>index</code> 是稳定 ID手动加帧后不连续不能用数组下标代替。</p>
<pre>KeyFrame {
index, timestamp, url,
description,
transparent_human_score,
cleaned_url, cleaned_applied,
quality_report,
scene_assets: SceneAsset[],
elements: KeyElement[],
storyboard: StoryboardScene,
generated_images: GeneratedImage[]
}</pre>
</div>
<div class="card">
<h3>TransparentHumanFrameScore</h3>
<p>透明骨架人主题的抽帧验收结果。只有 <code>target=transparent_human</code> 时会在抽帧阶段写入;普通抽帧目标不要求该字段。</p>
<pre>TransparentHumanFrameScore {
transparent_body_score: 0-25,
skeleton_visible_score: 0-25,
human_prominence_score: 0-15,
clarity_score: 0-15,
commercial_style_score: 0-10,
product_usefulness_score: 0-10,
total_score,
qualified,
reject_reason
}</pre>
</div>
<div class="card">
<h3>KeyElement</h3>
<p>从关键帧识别结果里确认出来的主体候选。当前素材准备流程只保留一个统一主体;多张关键帧通过 <code>source_frame_indices</code> 作为该主体的参考帧。</p>
<pre>KeyElement {
id,
name_zh, name_en, position,
source: auto | manual | region,
region,
cutouts: string[],
cutout_id,
subject_kind: object | living,
subject_assets: SubjectAsset[]
}</pre>
</div>
<div class="card">
<h3>AudioScript</h3>
<p>音频文案轨的结构化产物。<code>pipeline_transcribe</code> 提取 <code>audio.wav</code> 后按原音频秒数写入 SKG 英文产品介绍 voice-over再用 MiniMax T2A 从英文音色池随机生成配音文件。</p>
<pre>AudioScript {
status: idle | rewriting | completed | failed,
source_text,
source_zh,
rewritten_text,
product_brief,
rewrite_model,
voice_provider: minimax,
voice_model,
voice_id,
voice_url,
error
}</pre>
</div>
<div class="card">
<h3>SceneAsset / SubjectAsset</h3>
<p>画面工作台素材准备阶段生成的组图资产。实际图片保存在 <code>jobs/&lt;jobId&gt;/assets</code>,可作为 <code>asset</code> 类型复制到分镜槽位。</p>
<pre>SceneAsset {
id, label, url,
width, height, quality, size,
scene_mode: remove_subject | similar | style,
scene_style,
quality_report
}
SubjectAsset {
id, view, label, url,
background: white | black,
width, height, size,
source_frame_indices[]
}</pre>
</div>
<div class="card">
<h3>ProductLibraryItem</h3>
<p>内置 SKG 白底图库条目。实际图片保存在 <code>api/product_library/skg-products/images</code>,被选中时会复制到 <code>jobs/&lt;jobId&gt;/assets</code>,再以普通 <code>ImageRef(kind="asset")</code> 进入产品参考组。</p>
<pre>ProductLibraryItem {
id, handle, title, product_type,
image_index, filename, url,
width, height,
white_score,
source_path,
tags[]
}</pre>
</div>
<div class="card">
<h3>ProductFusionShot</h3>
<p>产品融合镜头组的单行数据。每个关键帧最多 6 行,用户选择一个内置角色后只微调场景/产品使用/享受描述和秒数;四张桌面 SKG 产品角度图会在顶部显式展示为真实产品真源,所选角色 7 张参考图作为人物身份参考,生成时作为 Seedance 参考图提交。</p>
<pre>ProductFusionShot {
id,
first_image,
last_image,
product_images[4],
action_text,
duration,
image_model: gpt-image-2,
video_model: seedance,
// legacy: product_image, person_image, product_region, scene_image, guide_image
}</pre>
</div>
<div class="card">
<h3>StoryboardScene</h3>
<p>分镜编排结果,不是复刻说明。它把参考图和 SKG 改造方向绑定到一个分镜上。</p>
<pre>StoryboardScene {
duration,
subject_image,
scene_image,
product_image,
action_image,
subject,
product,
scene,
action
}</pre>
</div>
</div>
</section>
<section id="api-map" data-search>
<h2>接口地图</h2>
<table>
<thead>
<tr><th>功能</th><th>接口</th><th>前端调用</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td>网页登录</td><td><code>POST /auth/login</code><code>GET /auth/check</code><code>POST /auth/logout</code></td><td><code>web/app/login/page.tsx</code>、Nginx <code>auth_request</code></td><td>登录页提交账号密码到 <code>/api/auth/login</code>,后端设置 HttpOnly 会话 Cookie生产 Nginx 对工作台和 <code>/api/</code><code>/auth/check</code> 做统一校验,未登录页面跳 <code>/login/</code>API 返回 JSON 401。</td></tr>
<tr><td>历史列表</td><td><code>GET /jobs</code></td><td><code>listJobs</code></td><td>所有 job 精简列表id/url/status/thumbnail/mtime…按 state.json mtime 倒序。前端 URL 无 <code>?job=</code> 时拉它回填全部历史;带 <code>limit</code> 可截断。</td></tr>
<tr><td>创建任务</td><td><code>POST /jobs</code></td><td><code>createJob</code></td><td>提交 TK 链接,后台开始下载,停在 downloaded 等用户点解析。</td></tr>
<tr><td>上传视频</td><td><code>POST /jobs/upload</code></td><td><code>uploadJob</code></td><td>保存 source.mp4然后同样进入下载完成状态。</td></tr>
<tr><td>删除输入视频</td><td><code>DELETE /jobs/{id}</code></td><td><code>deleteJob</code></td><td>从任务队列、URL 和磁盘 <code>jobs/&lt;id&gt;</code> 目录移除整个 job包括源视频、关键帧、元素提取图和生成视频。</td></tr>
<tr><td>解析视频</td><td><code>POST /jobs/{id}/analyze?frames=&amp;target=&amp;mode=&amp;quality=</code></td><td><code>analyzeJob</code></td><td>拆轨 + 目标化抽关键帧。默认 <code>frames=12</code><code>target</code> 支持透明骨架人、综合、清晰主体、转场变化、表情瞬间、动作峰值;当前 UI 默认 <code>transparent_human</code>。透明骨架人目标现在只走本地清晰度、中心主体、对比度、画面变化和 pHash 去重,不在抽帧阶段逐帧调用 Vision<code>mode=append</code> 追加新关键帧;<code>quality=auto</code> 为展示友好档,最高只自动选择精细,不会自动上极准;极准保留为手动选择。抽帧开始时同步拆出 <code>audio.wav</code> 并启动音频处理线程。多个抽帧请求进入后端队列顺序处理。</td></tr>
<tr><td>音频文案轨</td><td><code>POST /jobs/{id}/transcribe</code></td><td><code>triggerTranscribe</code></td><td>若尚未拆轨,先从 <code>source.mp4</code> 提取 <code>audio.wav</code> 并回填 <code>source_audio_url</code>;随后用原音频实际秒数估算英文词数,按 <code>AUDIO_PRODUCT_BRIEF</code> 生成有趣、自然的 SKG 英文产品介绍 <code>audio_script.rewritten_text</code>。ASR/翻译结果保留为改前对照和节奏参考;如果 ASR 不可用,也会用原音频时长继续生成产品口播。配置 <code>MINIMAX_API_KEY</code> 后调用 MiniMax T2A并从 <code>MINIMAX_TTS_VOICE_POOL</code> 随机选择男声、女声或成熟声生成 <code>audio_script.voice_url</code></td></tr>
<tr><td>原始音频文件</td><td><code>GET /jobs/{id}/audio.wav</code></td><td><code>sourceAudioUrl</code></td><td>返回拆轨得到的 wav底部 <code>AudioStrip</code> 拉取该文件,用 Web Audio API 解码并计算波形峰值。原音频播放器驱动时间轴,播放时全局指针和当前字幕节点内指针同步移动。</td></tr>
<tr><td>改写配音文件</td><td><code>GET /jobs/{id}/audio-script.mp3</code></td><td><code>apiAssetUrl(job.audio_script.voice_url)</code></td><td>返回 MiniMax T2A 生成的英文 mp3。没有配置 MiniMax 或生成失败时该文件不存在,但英文改写文案仍会保存在 <code>audio_script.rewritten_text</code></td></tr>
<tr><td>手动加帧</td><td><code>POST /jobs/{id}/frames?t=</code></td><td><code>addManualFrame</code></td><td>按视频时间戳抽一帧index 递增但 frames 按 timestamp 排序。</td></tr>
<tr><td>Vision 识别</td><td><code>POST /frames/{idx}/describe</code></td><td><code>describeFrame</code></td><td>写入 frame.description后续可从 objects 加候选元素。</td></tr>
<tr><td>清洗水印</td><td><code>POST /frames/{idx}/cleanup</code></td><td><code>cleanupFrame</code></td><td>支持全图和区域清洗,生成 cleaned 待应用版本;前端批量清洗会顺序调用该接口,不自动覆盖原图。单帧清洗状态按 frame.index 隔离,清洗某一张不会禁用其他关键帧的清洗按钮。</td></tr>
<tr><td>应用清洗版</td><td><code>POST /frames/{idx}/cleanup/apply</code></td><td><code>applyCleanedFrame</code></td><td>把 cleaned 待应用版本覆盖到原关键帧,并保留首次原图备份;前端“一键替换待应用”会顺序调用该接口应用所有已有清洗版。</td></tr>
<tr><td>应用清洗</td><td><code>POST /cleanup/apply</code></td><td><code>applyCleanedFrame</code></td><td>物理覆盖 frames/{idx}.jpg并备份原图。</td></tr>
<tr><td>元素增改删</td><td><code>POST/PATCH/DELETE /elements</code></td><td><code>addElement/updateElement/deleteElement</code></td><td>让用户修正 Vision 错误,避免候选结果锁死。</td></tr>
<tr><td>元素提取</td><td><code>POST /elements/{element_id}/cutout</code></td><td><code>cutoutElement</code></td><td>调用图像模型生成独立白底素材图,每次累积一张 cutout。</td></tr>
<tr><td>主体资产包</td><td><code>POST /elements/{element_id}/subject-assets</code></td><td><code>generateSubjectAssets</code></td><td>根据参考帧重新绘制一个统一主体资产包;前端默认把全部关键帧作为 <code>source_frame_indices</code>,如果用户手动选择了关键帧则只传已选帧,后端拼参考板。人物默认输出六张身份标准图,另有表情补充和动作补充分组可选;纯白/黑背景,不含其他元素,并裁去空白让主体占满画面。</td></tr>
<tr><td>首尾帧资产</td><td><code>POST /frames/{idx}/scene-asset</code></td><td><code>generateSceneAsset</code></td><td>同一接口兼容旧场景图和新首尾帧;新流程传 <code>asset_role=first_frame/last_frame</code>,后端走文字生图,参考帧只用于理解透明骨架人形象、比例、机位和光线,生成结果仍保存在 <code>scene_assets</code> 并自动填入产品融合镜头。</td></tr>
<tr><td>产品图库</td><td><code>GET /product-library/skg</code></td><td><code>listProductLibrary</code></td><td>读取内置 SKG 白底图库 manifest返回产品标题、品类、尺寸、白底评分和预览图 URL。</td></tr>
<tr><td>产品图入库到 job</td><td><code>POST /jobs/{id}/assets/product-library</code></td><td><code>copyProductLibraryAsset</code></td><td>把一个内置产品图库条目复制为当前 job 的普通 asset返回 <code>ImageRef(kind="asset")</code>,用于画面工作台产品融合和分镜产品参考组。</td></tr>
<tr><td>角色库</td><td><code>GET /character-library/skg</code></td><td><code>listCharacterLibrary</code></td><td>读取内置 5 个透明骨架人角色 manifest每个角色含正面、左右 45 度、侧面、背面、半身近景和背部特写 7 张参考图。</td></tr>
<tr><td>角色图入库到 job</td><td><code>POST /jobs/{id}/assets/character-library</code></td><td><code>copyCharacterLibraryAssets</code></td><td>把所选角色的 7 张参考图复制为当前 job asset返回 <code>subject_images</code>,产品融合生成视频时作为人物身份参考图提交。</td></tr>
<tr><td>产品融合引导图</td><td><code>POST /jobs/{id}/product-fusion/guide</code></td><td><code>createProductFusionGuide</code></td><td>旧流程兼容接口:读取产品图和白底人物图,按 <code>product_region</code> 合成位置引导图。当前内置角色 + 产品 + 描述流程不再主动调用它。</td></tr>
<tr><td>产品融合描述词</td><td><code>POST /jobs/{id}/product-fusion/descriptions</code></td><td><code>generateProductFusionDescriptions</code></td><td>兼容接口:可生成产品融合动作描述库。当前前端默认直接用本地 36 条镜头语言模板预填 6 行镜头,并通过“换一组”按钮按 6 条一组轮换。</td></tr>
<tr><td>分镜保存</td><td><code>PUT /frames/{idx}/storyboard</code></td><td><code>updateStoryboard</code></td><td>保存 4 图槽、时长和改造说明。</td></tr>
<tr><td>生图</td><td><code>POST /frames/{idx}/generate</code></td><td><code>generateImage</code></td><td>基于关键帧或已选生成图做 image-to-image目前可用。</td></tr>
</tbody>
</table>
</section>
<section id="node-contract" data-search>
<h2>节点职责边界</h2>
<table>
<thead>
<tr><th>节点</th><th>当前职责</th><th>不该承担</th><th>改动主要文件</th></tr>
</thead>
<tbody>
<tr>
<td><span class="tag blue">输入 Input</span></td>
<td>创建/上传任务,显示视频就绪;每个视频缩略图上方都有绑定自己的自动抽帧快捷工具条,默认只露出目标和抽帧按钮,张数/自动精度收进设置;也可在视频抽帧侧边面板内自动抽帧。多个视频抽帧可先后入队,切换 active 视频不会清空其他视频已选帧或关闭它们的异步生成回写。</td>
<td>不要自动一路跑到 ASR 或生图;用户需要控制解析节奏。</td>
<td><code>page.tsx</code><code>InputNode</code><code>VideoFramePanelNode</code><code>api/main.py</code></td>
</tr>
<tr>
<td><span class="tag violet">画面工作台 Visual Lab</span></td>
<td>作为素材准备看板:显示准备进度、质量风险、关键帧 / 统一主体包 / 场景图 / 分镜视频四个入口;上方缩略图按关键帧、主体包、场景图、视频任务分组。点击关键帧进入素材审核面板,点击资产图复制到分镜编排。</td>
<td>不要在主卡片里堆复杂表单;主卡片只做状态总览和入口。</td>
<td><code>VisualLabNode</code><code>FrameLightbox</code><code>generateSceneAsset</code><code>generateSubjectAssets</code>、视频任务接口</td>
</tr>
<tr>
<td><span class="tag violet">分镜工作台</span></td>
<td>每个分镜填 4 图槽和改造 brief为后续生成首帧/视频片段做准备。</td>
<td>当前不负责真实调用视频模型。</td>
<td><code>StoryboardWorkbench</code><code>updateStoryboard</code></td>
</tr>
<tr>
<td><span class="tag gray">Audio / ASR / Rewrite</span></td>
<td>独立声音文案轨:从 <code>source.mp4</code> 直接提取 <code>audio.wav</code>,按原音频时长生成 SKG 产品语境英文 voice-overASR/翻译保留为改前对照和节奏参考。MiniMax T2A 配置后从男声、女声、成熟声池随机生成自然英文配音 mp3。不再等待抽帧完成用户在主画布 <code>AudioNode</code> 点击卡片或“提取音频 / 重新提取音频”即可打开底部音频条并启动;即使视觉抽帧正在进行,也通过 <code>audio_script.status</code> 并行管理音频忙碌态。<code>AudioNode</code> 用“改前 · 原音频 / 改后 · SKG Product VO”摘要展示底部 <code>AudioStrip</code> 吸附屏幕底端,可拖拽调整高度,按时间段展示英文、中文翻译和波形;原音频播放时指针同步穿过字幕节点,右侧显示英文产品口播和 MiniMax 英文配音。</td>
<td>不要阻断视觉素材管线。</td>
<td><code>AudioNode</code><code>AudioStrip</code><code>ASRNode</code><code>TranslateNode</code><code>RewriteNode</code><code>pipeline_transcribe</code><code>AudioScript</code></td>
</tr>
<tr>
<td><span class="tag green">Video / Compose</span></td>
<td>视频任务状态展示在 Visual LabCompose 承载最终合成。</td>
<td>不要把 Compose 提前变成视频生成控制台。</td>
<td><code>VisualLabNode</code><code>/storyboard/video</code><code>generated_videos</code><code>ComposeNode</code></td>
</tr>
</tbody>
</table>
</section>
<section id="current-state" data-search>
<h2>当前已通与阻塞</h2>
<div class="grid-2">
<div class="card">
<h3>已通</h3>
<ul>
<li>TK 链接 / 上传创建 job。</li>
<li>视频下载或本地保存ffmpeg 抽关键帧。</li>
<li>手动按时间戳加关键帧。</li>
<li>关键帧清洗水印,全图或区域清洗。</li>
<li>Vision 识别关键帧,输出 scene、objects、style、suggested_prompt并作为主体候选来源。</li>
<li>主体候选确认、改名、删除和主体资产包生成。</li>
<li>分镜工作台 4 图槽和改造说明自动保存。</li>
<li>音频文案轨:点击提取音频后按原音频时长自动生成 SKG 英文产品介绍口播;配置 MiniMax 后从男声、女声、成熟声池随机生成自然英文配音 mp3。底部音频条可播放原音频并用指针逐段对齐字幕节点。</li>
<li>nano-banana-pro image-to-image 生图。</li>
</ul>
</div>
<div class="card">
<h3>阻塞 / 占位</h3>
<ul>
<li>ASR优先走当前 OpenAI-compatible 音频转写入口;如果该网关没有 <code>/audio/transcriptions</code>,自动 fallback 到 <code>ASR_FALLBACK_MODEL</code>(默认 <code>gemini-2.5-flash</code>)的多模态音频识别。</li>
<li>MiniMax当前接入的是官方 T2A 英文配音能力,不是 ASR默认随机音色池是 <code>English_magnetic_voiced_man</code><code>English_Upbeat_Woman</code><code>English_MaturePartner</code>。API Key 只能放本地环境变量,不能写入仓库。</li>
<li>Audio Product Brief默认是通用 SKG 放松产品卖点,后续可改成跟已选产品库条目联动。</li>
<li>Video Gen模型层按业务保留 Seedance / Kling / Veo/Voe 选择;后端已支持 Poe、火山方舟和 SKG 豆包视频网关。Seedance 可通过 <code>VIDEO_API_BASE_URL=https://ai.skg.com/doubao</code> 走 content JSON 异步任务,提交后写入 Video Gen 节点并轮询到完成。</li>
<li>Compose还没做本地 ffmpeg 字幕/TTS 合成。</li>
</ul>
</div>
</div>
<div class="callout warn" style="margin-top:14px">
<p>最重要的产品判断:当前视觉素材管线已经能继续推进,文案/音频/视频生成不要再反过来卡住镜头拆解和元素改造。</p>
</div>
</section>
<section id="request-language" data-search>
<h2>需求描述模板</h2>
<div class="todo">
<div class="todo-item">
<h3>改关键帧素材准备</h3>
<p>“我在关键帧素材准备面板里,主体候选应该怎么编辑/删除;主体资产包怎么生成;场景图怎么基于主体去除、换风格、审核、复制到分镜。”</p>
</div>
<div class="todo-item">
<h3>改 Storyboard 节点</h3>
<p>“我在 DAG 的元素改造节点它上方缩略图展示什么、hover 预览什么、点击后是否进入工作台、是否自动选中对应分镜。”</p>
</div>
<div class="todo-item">
<h3>改分镜工作台</h3>
<p>“我在顶部分镜头编排下拉面板,每个分镜需要哪些槽位、字段如何命名、保存后如何传给后续生成视频。”</p>
</div>
<div class="todo-item">
<h3>改数据/接口</h3>
<p>“这个动作需要持久化到 state.json字段加在 Job/KeyFrame/KeyElement/StoryboardScene 哪一层,刷新后要恢复。”</p>
</div>
<div class="todo-item">
<h3>改节点语义</h3>
<p>“这个节点的业务职责要改,不只是 UI 文案请同步更新节点标题、subtitle、说明、可点击行为、状态推导和本源码解析页。”</p>
</div>
</div>
</section>
<section id="change-log" data-search>
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-15 · 登录页只保留源码背景、登录和动画</h3>
<span class="tag rose">UI</span>
<span class="tag violet">Login</span>
</header>
<div class="body">
<p><strong>问题:</strong>登录页仍像内容页,保留了 SKG 标题、内容创作中枢、Pipeline 状态和底部指标,和“直接把源码背景写过来,上面只放登录和动画”的目标不一致。</p>
<p><strong>改动:</strong><code>web/app/login/page.tsx</code> 删除左侧营销文案、状态胶囊、标题、说明、Pipeline 和指标栏,只保留 <code>OasisCanvas</code>、动态角色透明框和登录表单。<code>web/app/globals.css</code> 新增 <code>login-page--source</code><code>login-source-overlay</code><code>login-source-character-panel</code> 等样式,让两个功能浮层直接叠在原版 Digital Oasis 背景上。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页草地鼠标转发、面板压缩和角色放大</h3>
<span class="tag rose">UI</span>
<span class="tag violet">Login</span>
</header>
<div class="body">
<p><strong>问题:</strong>原版 Digital Oasis 鼠标划过草地会产生响应,但登录页的表单和透明面板盖在 iframe 上方时iframe 收不到鼠标坐标;同时右侧登录面板过大,左侧动态角色被压成小装饰。</p>
<p><strong>改动:</strong><code>web/components/login/oasis-canvas.tsx</code> 在捕获阶段监听父页面指针移动,并通过 <code>postMessage</code> 发送到 <code>web/public/oasis-source/index.html</code>,后者复用原 raycaster 逻辑驱动草地。<code>web/app/login/page.tsx</code> 将身份验证面板改成 290-320px 窄列、去掉强制大高度、压缩标题/输入/按钮间距;<code>web/app/globals.css</code> 将动态角色面板放大成左侧透明框架里的主视觉。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/components/login/oasis-canvas.tsx</code><code>web/public/oasis-source/index.html</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页两块主面板复刻 Pillars 透明卡片质感</h3>
<span class="tag rose">UI</span>
<span class="tag violet">Login</span>
</header>
<div class="body">
<p><strong>问题:</strong>登录页左右两块面板仍偏旧黑色实体面板,没有用上源码 <code>Pillars</code> 区域的透明玻璃质感。</p>
<p><strong>改动:</strong><code>web/app/globals.css</code> 将左侧内容面板和右侧登录面板统一改为源码 <code>.pillar-card</code> 的核心效果:<code>rgba(10,18,10,.55)</code> 半透明底、<code>rgba(140,180,120,.12)</code> 细边、<code>16px</code> 圆角、<code>blur(16px)</code><code>0 8px 40px rgba(0,0,0,.4)</code> 阴影和 hover 边框/背景变亮输入框、动态图角色面板、Pipeline 胶囊和底部状态条也同步成同一透明体系。</p>
<p><strong>影响:</strong><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页改为 Digital Oasis 动态背景</h3>
<span class="tag rose">UI</span>
<span class="tag violet">Canvas</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户希望采用下载的 <code>remix-3d-website-the-digital-o</code> 视觉方向;登录页需要更有沉浸感,但不能回到具体产品展示。</p>
<p><strong>改动:</strong>新增 <code>web/public/oasis-source/index.html</code>,直接复制下载包原始 WebGPU / Three.js 草场源码;<code>web/components/login/oasis-canvas.tsx</code> 改为 iframe 承载这份源码,登录页只做上层文案和表单覆盖。源码页仅用 CSS 隐藏 demo 站自己的导航、文字和设置面板,保留原草场、景深、风动和鼠标交互。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/components/login/oasis-canvas.tsx</code><code>web/public/oasis-source/index.html</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页移除产品元素并保留动态模块</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>问题:</strong>登录页不应该放具体产品图,入口职责是进入内容生产系统,不是展示某个产品。</p>
<p><strong>改动:</strong>移除登录页产品图、G7/Neck Massager 等产品文案和产品摄影卡改为抽象流程视觉与全新入口文案内容创作中枢、Content Production System、Creative Pipeline。四个动态几何角色作为 <code>Live Creative Modules</code> 小组件保留,继续响应鼠标眼神跟随、输入、显示密码、错误和成功状态。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页吸收 skg.com 官网视觉元素</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>改动:</strong>参考 skg.com 官网的浅灰产品橱窗、黑白强对比、胶囊按钮、SKG 标识、米白/香槟金健康产品光感,重构生产登录页左侧展示区;角色舞台从纯暗色网格改为官网感浅灰产品展示台,底部模块文案改为 Shop by Need / Recovery / Self-Care 语义。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页角色视觉样式升级</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>改动:</strong>在保留四个几何角色结构的基础上,增加底座阴影、发光边、玻璃高光、内部面板和状态点,让角色更像 SKG 内容工作台里的小模块,而不是纯色几何块。</p>
<p><strong>影响:</strong><code>web/components/login/animated-login-characters.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页角色取消默认上下浮动</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>问题:</strong>角色舞台默认挂了 <code>login-stage-breathe</code> 无限循环,导致四个角色在登录页一直上下移动。</p>
<p><strong>改动:</strong>移除默认呼吸动画,角色平时固定贴底;错误态只保留短暂横向抖动,成功态保留一次性反馈动画。</p>
<p><strong>影响:</strong><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页角色组件重构</h3>
<span class="tag rose">UI</span>
<span class="tag violet">Refactor</span>
</header>
<div class="body">
<p><strong>改动:</strong>新增 <code>web/components/login/animated-login-characters.tsx</code>,把登录页四个角色从 <code>web/app/login/page.tsx</code> 抽成独立组件;角色用配置数组渲染,眼睛、嘴型和黄色 SVG 嘴不再散落在页面主体。动效状态从全页 <code>data-mood</code> 改为挂在角色舞台 <code>login-character-stage</code> 上,样式作用域更清晰。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/components/login/animated-login-characters.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页角色眼神幅度增强</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>改动:</strong>放大登录页动画角色的鼠标跟随瞳孔位移,并加大显示密码时眼睛整体看向目标的偏移;同时略微放大白眼球,避免瞳孔大幅移动时显得被裁切。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 登录页动画角色改回风格库原版几何结构</h3>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>问题:</strong>首版登录页把风格库的动画角色理解成圆润小球,视觉上偏幼稚,和 <code>14 动画角色登录</code> 的原始气质不一致。</p>
<p><strong>改动:</strong>对照风格库 <code>AnimatedCharacters.vue</code> 和 demo把角色重写为紫色高矩形、黑色矩形、橙色半圆、黄色圆角柱的贴底几何组合保留鼠标视线跟随并让输入、显示密码、错误、成功状态驱动 skew、眼睛和嘴型变化。</p>
<p><strong>影响:</strong><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 生产站点增加应用内登录页</h3>
<span class="tag gray">Runtime</span>
<span class="tag blue">Security</span>
<span class="tag rose">UI</span>
</header>
<div class="body">
<p><strong>问题:</strong>公司域名部署后任何人知道地址都能打开工作台并调用生成能力。</p>
<p><strong>改动:</strong>把浏览器 Basic Auth 改为应用内登录页:前端新增 <code>web/app/login/page.tsx</code>,参考风格库 <code>14 动画角色登录</code> 做四个几何角色、鼠标视线跟随、输入 / 显示密码 / 错误 / 成功状态反馈;后端新增 <code>/auth/login</code><code>/auth/check</code><code>/auth/logout</code>,使用 HttpOnly Cookie + HMAC 会话签名;生产 Nginx 通过 <code>auth_request</code> 保护工作台和 <code>/api/</code></p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/app/login/page.tsx</code><code>web/app/globals.css</code><code>web/lib/api.ts</code><code>docker-compose.prod.yml</code><code>deploy/nginx.conf</code><code>deploy/.env.production.example</code><code>api/.env.example</code><code>.project.json</code><code>RULES.md</code><code>docs/deploy-vps.md</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 公司域名生产部署配置</h3>
<span class="tag gray">Runtime</span>
<span class="tag blue">Deploy</span>
</header>
<div class="body">
<p><strong>改动:</strong>把生产入口确定为 <code>https://marketing.skg.com</code>DNS 已解析到 VPS <code>76.13.31.179</code>。新增 Docker Compose 生产配置:前端用 Next 静态导出 + Nginx<code>/api/</code> 反代到唯一容器名 <code>skg-marketing-api:4291</code>,避免与 Coolify 网络里其他项目的泛名 <code>api</code> 冲突;后端任务目录持久化到服务器 <code>./data/jobs</code>Traefik 通过既有 <code>coolify</code> 外部网络接管 80/443。已完成上线验证HTTPS 首页 200<code>/api/health</code> 返回 <code>ok:true</code></p>
<p><strong>影响:</strong><code>Dockerfile.web</code><code>Dockerfile.api</code><code>docker-compose.prod.yml</code><code>deploy/nginx.conf</code><code>deploy/.env.production.example</code><code>docs/deploy-vps.md</code><code>.project.json</code><code>RULES.md</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-15 · 本地启动改为后台不弹 Terminal</h3>
<span class="tag gray">Runtime</span>
</header>
<div class="body">
<p><strong>问题:</strong>通过 macOS Terminal 启动后端会每次弹出一个终端窗口,打开页面时干扰使用。</p>
<p><strong>改动:</strong>新增 <code>scripts/start-dev-background.sh</code><code>scripts/stop-dev-background.sh</code>。启动脚本自动检查前端 4290 和后端 4291缺哪个后台启动哪个日志写入 <code>.logs/</code>PID 写入 <code>.pids/</code>,以后无需通过 <code>osascript</code> 打开 Terminal。</p>
<p><strong>影响:</strong><code>scripts/start-dev-background.sh</code><code>scripts/stop-dev-background.sh</code><code>.gitignore</code><code>RULES.md</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合按真实产品外置合成</h3>
<span class="tag orange">产品融合</span>
<span class="tag blue">Video Gen</span>
</header>
<div class="body">
<p><strong>问题:</strong>Seedance 生成时可能把颈部按摩仪当成可变形装饰物,导致产品样式变化、穿进透明身体或与骨架融合。</p>
<p><strong>改动:</strong>产品融合 prompt 改为英文硬约束:四张 SKG 产品图是真实产品照片和唯一实物真源,生成时按外置刚性设备做 product placement / visual compositing产品必须停留在透明皮肤外侧后颈外侧承托、两端沿左右颈侧向前保留遮挡、接触阴影、透视和真实比例禁止 x-ray blending、穿模、融进骨架或重绘成其他颈带。</p>
<p><strong>界面:</strong>产品融合顶部常驻显示桌面四张真实产品角度图,并支持鼠标停留放大查看。每行视频结果不再只显示最新一个,而是按历史持续追加横向结果条,鼠标停留任一结果会放大预览。</p>
<p><strong>后端:</strong>生视频参考图顺序调整为主参考图之后优先传四张产品图,再传其余人物角色图,提高真实产品外观权重。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/lightbox.tsx</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 修正 SKG 豆包视频网关路径</h3>
<span class="tag orange">Video Gen</span>
<span class="tag blue">Seedance</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品融合视频能进入队列,但后台任务 5% 后失败,错误为 <code>/contents/generations/tasks</code> 返回 404。</p>
<p><strong>根因:</strong><code>https://ai.skg.com/doubao</code> 这个 SKG 豆包网关的真实视频入口是 <code>/api/v3/contents/generations/tasks</code>,不是火山方舟直连 base 下使用的 <code>/contents/generations/tasks</code></p>
<p><strong>改动:</strong>后端默认路径识别 <code>ai.skg.com/doubao</code> 并自动使用 <code>/api/v3/contents/generations/tasks</code><code>/api/v3/contents/generations/tasks/{id}</code><code>/api/v3/contents/generations/tasks/{id}/content</code>;本机 <code>api/.env</code><code>api/.env.example</code> 同步更新。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env</code><code>api/.env.example</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合改为内置角色 + 产品 + 描述生成</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
<span class="tag blue">角色库</span>
</header>
<div class="body">
<p><strong>问题:</strong>当前产品融合不再需要手动首帧/尾帧,用户要的是从内置透明骨架人角色、场景描述、产品使用方式和享受状态直接生成视频。</p>
<p><strong>改动:</strong>桌面 <code>skg_anatomy_characters_20260514_120852</code> 的 5 个角色、35 张图内置为 <code>api/character_library/skg-characters</code>。产品融合页新增角色下拉和角色预览,每行只保留场景/产品使用/享受描述、秒数、生成按钮和结果视频选择不同角色时6 行描述会自动改成对应角色的场景气质、产品使用动作和享受状态,并自主写入“本镜头主要参考哪几张角色图、生成什么场景”。生成前自动复制所选角色 7 张参考图和固定 4 张 SKG 产品图到当前 job。</p>
<p><strong>后端:</strong>新增 <code>GET /character-library/skg</code><code>GET /character-library/skg/images/{filename}</code><code>POST /jobs/{job_id}/assets/character-library</code>。视频提交新增 <code>subject_images</code>,无首帧时主人物图以 <code>reference_image</code> role 传入 Ark/Seedance而不是强制作为 <code>first_frame</code></p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/character_library/skg-characters</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合内置多组镜头语言</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品融合不能只写“人物使用产品”,还要稳定控制出场方式、景别、运镜、产品入画、佩戴贴合、使用感受和收尾;同时 SKG 产品是颈部/肩颈按摩仪,比例和佩戴位置不能跑偏。</p>
<p><strong>改动:</strong>前端内置 36 条产品融合镜头语言模板6 条为一组,对应建立出场、产品入画、佩戴贴合、使用感受、生活延展和收尾记忆;产品融合页新增“换一组”按钮,只刷新 6 行描述词,不改变已选角色和视频结果。视频 prompt 额外写入颈部/后颈/颈肩使用部位和真实尺寸比例约束。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/app/page.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 音频提取直接生成英文产品口播</h3>
<span class="tag gray">Audio</span>
<span class="tag green">MiniMax</span>
</header>
<div class="body">
<p><strong>问题:</strong>“提取音频”不能只做原音频转文字再改写,用户需要点击后直接得到介绍 SKG 产品的英文文案和配音,长度尽量贴近原音频,并且声音不能生硬。</p>
<p><strong>改动:</strong><code>pipeline_transcribe</code> 提取 <code>audio.wav</code> 后读取原音频时长,用该时长估算英文口播词数;<code>_rewrite_audio_script_sync</code> 改为生成自然、有趣、可直接 TTS 的 SKG 英文产品介绍。ASR/翻译保留为对照和节奏参考ASR 不可用时仍继续生成产品口播。MiniMax voice_id 改为从 <code>MINIMAX_TTS_VOICE_POOL</code> 随机选择男声、女声或成熟声。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>api/README.md</code><code>RULES.md</code><code>web/components/nodes/index.tsx</code><code>web/components/audio-strip.tsx</code><code>web/components/dashboard.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合描述词扩成 20 条精准模板</h3>
<span class="tag orange">产品融合</span>
<span class="tag blue">Prompt</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品融合视频的动作描述不能泛泛写“人物使用产品”,需要稳定表达透明骨架人在具体场景中佩戴 SKG 产品,并呈现舒适享受状态。</p>
<p><strong>改动:</strong>前端内置 20 条产品使用描述模板,覆盖卧室、客厅、办公、浴室、阳台、影棚、阅读角等场景;当前产品融合页默认把前 6 条预填到 6 行镜头,用户只在需要时直接改每行描述。</p>
<p><strong>后端:</strong><code>generateProductFusionDescriptions</code> 的兜底模板同步扩为 20 条LLM 提示也改为生成 20 条 35-70 字描述,要求包含场景、佩戴/展示动作和舒适表情,同时排除医疗治疗承诺。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合改为首尾帧加四产品角度垫图</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
</header>
<div class="body">
<p><strong>问题:</strong>原产品融合依赖白底人物、手动画区域、场景图和融合引导图,但当前透明骨架人二创流程更需要文字生成首尾帧,再把产品真源作为垫图传给视频模型。</p>
<p><strong>改动:</strong>“场景图”页签改名为“首尾帧”,右侧用地点、风格、参考要素和 prompt 生成首帧/尾帧,生成后自动填入当前产品融合镜头。产品融合 6 行工作表改为首帧、尾帧、四张同一产品不同角度图、描述词、秒数和生成按钮,并提供桌面四角度填当前镜头/填满 6 镜头。</p>
<p><strong>后端:</strong><code>generateSceneAsset</code> 新增 <code>asset_role</code><code>first_frame/last_frame</code> 走文字生图并标记资产角色;<code>ProductFusionShot</code> 新增 <code>first_image</code><code>last_image</code><code>product_images</code>,视频提交直接把首尾帧和四张产品图交给 Seedance产品库新增桌面 <code>skg产品1-4.jpg</code> 四角度条目。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 本地抽帧改为展示友好算力档</h3>
<span class="tag orange">抽帧</span>
<span class="tag gray">Audio</span>
</header>
<div class="body">
<p><strong>问题:</strong>透明骨架人目标逐帧调用 Vision 验收会拖慢抽帧;切回本机算力后,如果自动档直接跑最高极准,也可能在展示时占满机器资源。</p>
<p><strong>改动:</strong><code>transparent_human</code> 目标保留,但抽帧阶段只走本地扫描、评分、去重和时间覆盖,不再逐帧调用 Vision。<code>quality=auto</code> 最高只自动选择精细;极准仍保留为手动选项。抽帧开始拆出 <code>audio.wav</code> 后会启动独立音频线程,视觉抽帧和音频处理并行,互不标失败。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 修复 ReactFlow Hydration 和后端 reload 卡住</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">Dev Server</span>
</header>
<div class="body">
<p><strong>问题:</strong>刷新页面时 Next 报 Hydration mismatchReactFlow 节点服务端默认宽度和客户端 localStorage 里的用户调整宽度不一致;同时提交抖音链接时若后端正在 <code>--reload</code> 等后台任务结束,会出现 4291 端口占用但请求卡住。</p>
<p><strong>改动:</strong><code>web/app/page.tsx</code> 增加 <code>clientReady</code>ReactFlow 和底部音频条只在浏览器挂载后渲染,避免服务端 HTML 和客户端本地布局缓存不一致。后端启动说明改为不带 <code>--reload</code> 的稳定命令,并更新 <code>RULES.md</code><code>api/README.md</code> 和本页运行入口。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>RULES.md</code><code>api/README.md</code><code>docs/source-analysis.html</code>。本地 4291 后端已按无 reload 模式重启。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 音频处理改为音频卡片手动触发</h3>
<span class="tag gray">Audio</span>
<span class="tag blue">Workflow</span>
</header>
<div class="body">
<p><strong>问题:</strong>等待抽帧完成后自动启动音频,不符合“先把声音文案拿出来审核”的工作流;用户需要在音频卡片上直接触发。</p>
<p><strong>改动:</strong>移除前端抽帧完成后的自动转写逻辑;<code>AudioNode</code> 保留并固定显示“提取音频 / 重新提取音频”按钮,点击音频卡片也会立即打开底部音频条。后端 <code>/transcribe</code> 不再要求 <code>frames_extracted</code>,视频就绪后可直接从 <code>source.mp4</code> 拆出 <code>audio.wav</code>,并按原音频时长生成 SKG 英文产品介绍和 MiniMax 随机英文配音;抽帧中触发时不抢主状态,而是用 <code>audio_script.status</code> 表示音频处理中。当当前网关的 <code>whisper-1</code> audio endpoint 返回 404 时,会 fallback 到 Gemini 多模态音频识别ASR 不可用时也会继续按原音频时长生成产品口播,不把可用文案标成前端错误。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 新增底部可伸缩音频条</h3>
<span class="tag gray">Audio</span>
<span class="tag violet">Timeline</span>
</header>
<div class="body">
<p><strong>问题:</strong>音频和文案只在节点或侧栏里展示,审核时缺少“文字和声音时间轴对应”的空间;英文口播和中文翻译也没有上下对齐。</p>
<p><strong>改动:</strong>新增 <code>web/components/audio-strip.tsx</code>,在主工作台底部吸附显示,可拖拽调整高度、可收起。每个音频段按时间横向排列,上方显示英文,中间显示中文翻译,下方显示对应波形条;底部原音频播放器驱动时间轴,播放时绿色指针会沿全局波形移动,并在当前字幕节点内同步走过该段。右侧显示按原音频时长生成的 SKG 英文产品口播、MiniMax 随机英文配音和产品依据。后端新增 <code>source_audio_url</code><code>GET /jobs/{id}/audio.wav</code> 只读接口,前端用 Web Audio API 解码生成波形峰值。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/audio-strip.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 音频结果改为改前/改后对照展示</h3>
<span class="tag gray">Audio</span>
<span class="tag violet">UI</span>
</header>
<div class="body">
<p><strong>问题:</strong>音频识别成功后只显示改写文案,用户看不到它和原音频之间的变化关系,难以判断“是不是把参考视频转成我们自己的话”。</p>
<p><strong>改动:</strong><code>AudioNode</code> 增加轻量对照摘要:改前显示原音频识别/翻译预览,改后显示 SKG 英文产品口播;侧栏 <code>Rewrite</code> 面板改为完整审核视图,先列原音频逐段 ASR/翻译,再列英文产品介绍稿、产品卖点依据和 MiniMax 英文配音播放器。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 多视频工作流状态按 job 隔离</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">Job State</span>
</header>
<div class="body">
<p><strong>问题:</strong>同时上传多个视频后,前端把已选关键帧和关键帧详情面板作为全局状态保存;切换 active 视频会清空选中帧,或者让详情面板指向另一个视频的同序号帧,容易误以为生图/自动化被终止或串任务。</p>
<p><strong>改动:</strong><code>web/app/page.tsx</code><code>selectedFrames</code><code>expandedFrame</code> 改为按 <code>jobId</code> 存储。切换视频只改变当前视图,不清空其他视频的选择;重新抽帧、删帧、手动加帧只清理或更新对应 job。异步生图、生视频、产品融合返回后按返回的 <code>job.id</code> 写回 <code>jobs[]</code>,不会落到切换后的 active job。轮询条件也把 <code>audio_script.status=rewriting</code> 纳入运行态,保证音频改写/配音阶段切换视频后仍继续刷新。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>docs/source-analysis.html</code>。后端轮询本来已经覆盖所有运行中的 job这轮主要修正前端 UI 工作上下文。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 抽帧后台任务不再卡住 API</h3>
<span class="tag blue">API</span>
<span class="tag orange">抽帧</span>
</header>
<div class="body">
<p><strong>问题:</strong>点击视频抽帧时,后端 4291 端口能连接但 <code>/health</code> 和后续请求长时间不返回,前端看起来像按钮没有反应。</p>
<p><strong>原因:</strong><code>pipeline_download</code><code>pipeline_analyze</code> 声明为 async background task但内部实际是同步 <code>yt-dlp</code><code>ffmpeg</code> 和 Vision 验收Starlette 会在事件循环里执行 async background task导致长抽帧把 API 主循环堵住。</p>
<p><strong>改动:</strong>下载和抽帧 pipeline 改为普通同步函数,让 FastAPI/Starlette 按线程池后台任务执行;<code>analyze_queue_worker</code> 也改为同步 worker。服务启动恢复时如果磁盘里有重启前遗留的 <code>downloading</code><code>splitting</code><code>transcribing</code> 运行态,会恢复成可重试状态,避免按钮一直 disabled。开发运行时改用不带 <code>--reload</code> 的后端命令,避免重载等待后台任务。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>docs/source-analysis.html</code>。已重启本地 4291 后端并验证 <code>/health</code> 立即返回;遗留的 <code>8b37e65521a6</code> job 已恢复为 <code>downloaded</code>,可重新点击抽帧。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 生视频接入 SKG 豆包网关</h3>
<span class="tag orange">Video Gen</span>
<span class="tag blue">Seedance</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户提供 <code>https://ai.skg.com/doubao</code> 作为视频模型网关;该网关应按 Seedance / 方舟内容生成任务格式提交,而不能误走普通 multipart 上传。</p>
<p><strong>改动:</strong><code>video_uses_ark()</code> 现在同时识别火山方舟域名和 <code>ai.skg.com/doubao</code>,统一使用 <code>content</code> JSON文本 prompt、首帧、尾帧和产品参考图作为不同 role 传入。火山方舟直连按 <code>/contents/generations/tasks/{id}</code> 轮询SKG 豆包网关按 <code>/api/v3/contents/generations/tasks/{id}</code> 轮询。<code>api/.env.example</code> 增加 SKG 豆包视频网关配置示例;生视频轮询上限改为 <code>VIDEO_POLL_TIMEOUT_SECONDS</code>,默认 900 秒,避免慢任务过早失败。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>docs/source-analysis.html</code>。本机 <code>api/.env</code> 已配置 <code>VIDEO_API_BASE_URL=https://ai.skg.com/doubao</code> 和视频专用 key。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 音频处理接入 SKG 英文产品口播与 MiniMax 配音</h3>
<span class="tag gray">Audio</span>
<span class="tag green">MiniMax</span>
</header>
<div class="body">
<p><strong>问题:</strong>音频处理节点之前只说明“音轨 → ASR → 翻译 → 改写”,没有按原音频时长生成的产品介绍产物,也没有配音输出;用户无法直接拿到符合 SKG 产品语境的英文口播。</p>
<p><strong>改动:</strong><code>Job</code> 新增 <code>audio_script</code><code>pipeline_transcribe</code> 提取 <code>audio.wav</code> 后按原音频秒数生成 SKG 英文产品介绍文案,并在配置 <code>MINIMAX_API_KEY</code> 时调用 MiniMax T2A 输出 <code>/jobs/{id}/audio-script.mp3</code>。MiniMax voice_id 从英文男声、女声、成熟声池随机选择;前端 <code>AudioNode</code> 和侧栏 Rewrite 区显示模型链路、英文产品文案和配音播放器。</p>
<p><strong>边界:</strong>MiniMax 官方 Speech API 当前接入的是 TTS 配音,不替代 ASR原始音频文案提取仍走现有 OpenAI-compatible audio transcription 入口。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>api/README.md</code><code>web/lib/api.ts</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>web/app/page.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 默认抽帧张数改为 12 帧</h3>
<span class="tag violet">InputNode</span>
<span class="tag blue">抽帧</span>
</header>
<div class="body">
<p><strong>问题:</strong>透明骨架人主题需要更稳定的素材覆盖,默认 5 帧太少,容易缺少可用于主体、场景和产品融合的角度。</p>
<p><strong>改动:</strong>后端 <code>KEYFRAME_COUNT</code> 默认值、前端抽帧 fallback、API client 默认参数都改为 12抽帧设置里的张数选项把 12 放到第一位。透明骨架人目标仍会对每个候选做 Vision 验收,不合格候选自动换下一帧。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 抽帧新增透明骨架人 AI 验收目标</h3>
<span class="tag violet">InputNode</span>
<span class="tag blue">Vision</span>
</header>
<div class="body">
<p><strong>问题:</strong>透明人二创不能只靠清晰度抽帧,也不能只要出现“骨头”就算合格;需要确认同一人形角色同时具备透明/半透明外壳、干净白色骨架、足够大且清晰、非恐怖广告感。</p>
<p><strong>改动:</strong><code>FrameExtractTarget</code> 新增 <code>transparent_human</code> 并设为当前 UI 默认目标。后端抽帧先按本地清晰度、中心主体、对比度和去重扩大候选池,再逐张从原视频抽高清帧交给 Vision 评分;评分维度包括透明身体、可见骨架、人物占比、清晰度、商业风格和产品可用性。不合格帧会被删除并自动换下一候选,直到凑够目标张数或候选耗尽。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/lightbox.tsx</code><code>web/lib/workflow-target.ts</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 清洗页增加一键替换待应用清洗版</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">清洗</span>
</header>
<div class="body">
<p><strong>问题:</strong>一键清洗后会生成多张待应用清洗版,但用户仍需要逐张点“替换原图”,素材准备阶段操作成本偏高。</p>
<p><strong>改动:</strong>“原图/清洗”页新增“待替换清洗版”状态和“一键替换待应用 N 张”按钮,并显示批量替换进度和失败数。该按钮只应用已经存在的清洗结果,不重新清洗;不满意的帧仍可先单张重洗或丢弃。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合收敛为首尾帧 + 固定四产品图</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品融合页继续显示产品角度槽、辅助栏和产品图库会把操作变复杂;当前工作流只需要用户手动补人物首尾帧,产品图固定来自桌面 4 张 SKG 图。</p>
<p><strong>改动:</strong>“产品融合”页每行只保留首帧、尾帧、已预填描述词、秒数、生成按钮和行末视频结果。生成单条或批量视频前,前端自动把内置的 4 张桌面 SKG 产品图复制为当前 job asset 并写入 <code>product_images[4]</code>;视频 prompt 增加 <code>产品融合镜头ID</code> 标记,用来把生成结果显示回对应行。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合镜头组改为纵向 6 行工作表</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品融合要生成 6 条视频,但旧排版只在列表里显示摘要,真正编辑区只展开当前镜头,用户无法从上到下同时检查 6 条镜头的产品、人物、场景和描述是否一一对应。</p>
<p><strong>改动:</strong>“产品融合”页左侧改为纵向 6 行镜头工作表。每行包含产品图槽、白底人物图槽、人物图内产品区域画框、场景图槽、动作描述、秒数选择和单条生成按钮右侧只保留模型状态、当前镜头状态、AI 草拟 6 条、批量排队和当前镜头产品图库。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合改为 6 行区域约束镜头组</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">Seedance</span>
</header>
<div class="body">
<p><strong>问题:</strong>只把产品图作为参考图无法解决尺寸和位置融合,模型不知道产品应该放在人物或场景里的哪个区域。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 的“产品融合”页改为 6 行镜头组。每行绑定产品图、白底人物图、手动画出的产品区域、场景图、描述词和视频秒数;图片槽支持上传和粘贴,产品图也可从内置 SKG 白底图库选用。右侧固定显示图片模型 <code>GPT Image 2</code> 和视频模型 <code>Seedance</code>,支持 AI 草拟 6 条动作描述和批量排队;单条生成入口放在每一行镜头内。</p>
<p><strong>后端:</strong>新增 <code>POST /jobs/{job_id}/product-fusion/guide</code><code>POST /jobs/{job_id}/product-fusion/descriptions</code>。前者把产品图按 <code>product_region</code> 合成到白底人物图上,生成普通 <code>asset</code> 引导图;后者用 LLM 或本地模板生成 6 条动作描述草稿。前端再把引导图作为 Seedance 首帧,并把产品图、人物图、场景图作为参考图提交。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合槽位接入应用内剪贴板</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Clipboard</span>
</header>
<div class="body">
<p><strong>问题:</strong>卡片上的“复制”写入的是应用内 <code>ImageRef</code> 剪贴板,而产品融合槽位只监听系统剪贴板文件,导致复制后的素材无法在产品融合里粘贴。</p>
<p><strong>改动:</strong><code>NodeData</code><code>FrameLightbox</code> 新增 <code>clipboard</code> 传递链路;产品融合三类槽位的“粘贴”按钮优先使用应用内剪贴板,应用剪贴板为空时再提示可选中槽位后 Cmd+V 粘贴系统图片。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 增加产品融合和 SKG 内置白底图库</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag orange">产品融合</span>
</header>
<div class="body">
<p><strong>问题:</strong>生成视频需要稳定的 SKG 产品真源,不能每次都依赖临时上传或从参考视频里找产品图;桌面已有整理过的 SKG 产品图,应作为内置数据库使用。</p>
<p><strong>改动:</strong>从桌面 <code>skg_product_downloads/all_products</code> 的 gallery 中筛出 41 张白底产品图,生成 <code>api/product_library/skg-products/manifest.json</code> 和压缩预览图。<code>FrameLightbox</code> 新增“产品融合”页签,<code>StoryboardWorkbench</code> 的 SKG 产品参考区也接入同一个 <code>ProductLibraryPicker</code>,支持搜索、品类筛选、尺寸预览和一键加入。</p>
<p><strong>后端:</strong>新增 <code>GET /product-library/skg</code><code>GET /product-library/skg/images/{filename}</code><code>POST /jobs/{job_id}/assets/product-library</code>。选中库内产品图时,后端会复制成当前 job 的 <code>asset</code>,后续仍通过既有 <code>product_images</code> 进入生视频接口。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/product_library/skg-products</code><code>web/lib/api.ts</code><code>web/components/product-library-picker.tsx</code><code>web/components/lightbox.tsx</code><code>web/components/storyboard-workbench.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 场景图改为全图参考和关键词 Prompt</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Prompt</span>
</header>
<div class="body">
<p><strong>问题:</strong>场景图页不能只围绕当前单张图;它需要看到全部关键帧,并通过地点、风格、参考要素等关键词组合出可控 prompt再生成场景。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 的“场景图”页左侧改为全部关键帧网格:点击图片设为生成目标,点击“选”加入场景参考。右侧新增地点、生成方式、风格、额外关键词和参考要素 chips并自动拼出可编辑场景 prompt。</p>
<p><strong>后端:</strong><code>generateSceneAsset</code> 请求新增 <code>prompt</code><code>source_frame_indices</code>;多张参考帧会拼成 contact sheet 给图像模型,同时把用户 prompt 注入场景生成提示词。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 主体资产改为参考重绘六张标准图</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Assets</span>
</header>
<div class="body">
<p><strong>问题:</strong>主体资产不是抠图,也不是只看当前单帧生成多角度;主体页需要看到全部参考帧,并用这些参考重新绘制一个完整主体。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 在“主体资产”页左侧显示参考帧网格,优先纳入所有已清洗帧,额外已选帧也会并入;小图排列,可点击切换当前帧。右侧仍负责统一主体确认和生成。人物/生物默认视图改为六张身份标准图:正面、背面、左侧、右侧、左前 45°、右前 45°并把表情补充和动作补充折成独立分组需要时再勾选。</p>
<p><strong>后端:</strong><code>generateSubjectAssets</code> prompt 改为“参考重绘”,明确禁止裁剪/抠图/粘贴源像素,要求主体完整居中、纯白/黑背景、无其他元素,并占画面约 85-95% 高度;落盘时会裁掉纯背景空白并放大主体。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 主体资产改为统一主体参考帧生成</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Workflow</span>
</header>
<div class="body">
<p><strong>问题:</strong>当前流程里抽多张关键帧的目的不是让每张图各自生成一个主体,也不是从单张图硬造多角度,而是用多张关键帧共同还原同一个主体的多角度、动作和表情。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 的主体页改成“统一主体”逻辑:全局只保留一个主体候选;生成主体资产包时默认传全部关键帧作为 <code>source_frame_indices</code>,用户手动选择关键帧时只传已选帧。场景图仍然按当前关键帧逐张生成,因此是一个主体包 + 多个场景图。</p>
<p><strong>后端:</strong><code>generateSceneAsset</code> 查找主体名称时改为从整个 job 的已生成主体资产中读取,而不是只看当前帧,确保任意关键帧生成场景图时都能移除统一主体。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 场景图改为主体资产之后生成</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>场景图如果先于主体资产生成,只能做普通背景清理,无法准确知道要移除哪个主体,也不利于后续生成相似但不同或同构换风格的场景。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 页签顺序改为“原图/清洗 → 主体资产 → 场景图 → 审核”;画面工作台缩略图和进度文案也同步为主体资产先于场景图。场景图页新增“去主体原场景 / 相似新场景 / 同构换风格”和风格选择,且在没有主体资产时提示先生成主体资产。</p>
<p><strong>后端:</strong><code>generateSceneAsset</code> 请求新增 <code>scene_mode</code><code>scene_style</code>;后端提示词会优先读取已生成主体资产对应的主体名称,生成去主体并补背景的场景图,再按模式决定是否做相似变化或风格变化。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 关键帧素材面板统一右侧操作栏</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Layout</span>
</header>
<div class="body">
<p><strong>问题:</strong>“原图/清洗、主体资产、场景图、审核”都应遵循同一结构:左侧负责看图和框选,右侧负责操作、状态和结果;旧布局把部分操作塞在左侧下方,导致左侧满、右侧空。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 统一为左侧主图、右侧操作栏。清洗按钮、批量清洗、清洗结果预览、场景图生成/复制、主体识别/主体资产包和审核状态都在右侧;切换到非清洗页时会退出框选模式,避免画框状态残留。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 单帧清洗不再全局锁住其他帧</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Bugfix</span>
</header>
<div class="body">
<p><strong>问题:</strong>单独清洗某一张关键帧时,前端使用全局 <code>cleaning</code> 布尔状态,导致切到其他关键帧后清洗按钮仍被禁用。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 改用 <code>cleaningFrameIds</code><code>frame.index</code> 记录正在清洗的帧,只禁用当前正在清洗的那一张;其他帧可以继续单独清洗。区域清洗完成时也只在用户仍停留于同一帧时清空当前框选,避免异步完成误清别的帧操作状态。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 清洗页支持一键批量清洗</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>画面工作台里的关键帧已经在抽帧阶段筛过,逐张点“清洗水印”会拖慢素材准备;但自动清洗又不能直接覆盖原图,否则模型误改时不好回退。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 的“原图/清洗”页新增“批量清洗”卡片,一键顺序清洗所有未处理素材帧,并显示进度和失败数。批量清洗只生成 <code>cleaned</code> 待审核版本,不自动应用;没清干净的帧再用单帧框选区域做手工补洗。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code>。后端暂未新增 batch API前端复用 <code>POST /jobs/{id}/frames/{idx}/cleanup</code> 顺序执行。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 关键帧详情改为素材准备面板</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>面板标题仍叫“关键帧详情 · 元素提取”,里面还露出普通 cutout 抠图、AI 提取、元素清单等旧流程入口;“选用此帧 / 加入目标帧”也无法说明实际价值,因为抽帧阶段已经筛过图,进入画面工作台的关键帧默认就应该参与素材准备。</p>
<p><strong>改动:</strong><code>KeyframePanelNode</code> 标题改为“关键帧素材准备”;<code>FrameLightbox</code> 的主体页改成“主体识别 / 主体清单 / 主体资产包”移除普通抠图列表、AI 提取按钮、详情内的目标帧开关以及手动/框选加主体入口,改为只显示“已在素材准备流程”的状态说明;<code>VisualLabNode</code> 上方缩略图也不再展示普通抠图分组,只展示关键帧、主体包、场景图和视频任务。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。底层旧 cutout 数据和接口暂保留兼容历史任务,但不再作为新素材准备流程的可见入口。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 画面工作台改为素材准备看板</h3>
<span class="tag violet">Visual Lab</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>画面工作台从展示缩略图扩展为素材生产中枢后,关键帧、主体资产包、场景图和视频任务继续混在一个列表里会让流程不清晰;关键帧详情面板也把清洗、识别、主体生成和场景生成都堆在一屏。</p>
<p><strong>改动:</strong><code>VisualLabNode</code> 改成素材准备进度看板,显示关键帧素材、主体资产、场景图和分镜/视频四个入口。<code>FrameLightbox</code> 新增“原图/清洗、主体资产、场景图、审核”四个页签,素材审核信息从普通列表中拆出来。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code>。这轮只重排工作台信息架构,批量自动准备队列仍留到下一阶段。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 画面工作台增加主体资产包和场景图</h3>
<span class="tag violet">Visual Lab</span>
<span class="tag blue">Assets</span>
</header>
<div class="body">
<p><strong>问题:</strong>抽帧阶段已经筛过图,画面工作台第一步应把已选关键帧转成可用于生视频的干净素材:同一主体一套多视角/动作/表情图,再基于主体生成每帧去主体场景图,而不是继续手动逐张抠普通 cutout。</p>
<p><strong>改动:</strong><code>KeyFrame</code> 新增 <code>scene_assets</code><code>quality_report</code><code>KeyElement</code> 新增 <code>subject_kind</code><code>subject_assets</code>。后端新增 <code>generateSubjectAssets</code><code>generateSceneAsset</code>,主体资产支持白/黑背景、原尺寸/固定尺寸、物体视角、人物/生物动作与喜怒哀乐等表情;当已选关键帧共同指向一个主体时,前端会把这些帧作为 <code>source_frame_indices</code> 传入,后端拼接参考板。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。生成出的素材保存在 <code>jobs/&lt;jobId&gt;/assets</code>,可作为 <code>asset</code> 类型复制到后续分镜槽位。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 抽帧精度自动选择并支持后端排队</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Queue</span>
</header>
<div class="body">
<p><strong>问题:</strong>抽帧精度不应该每次都让用户判断;点击一个视频抽帧后,其他视频不应被全局禁用,而应该可以先后排队。另外打开视频抽帧侧边面板后,也应能自动抽帧。</p>
<p><strong>改动:</strong><code>quality</code> 新增 <code>auto</code> 默认值,后端按 CPU 核数、内存和视频时长解析为快速或精细为了展示稳定auto 不再自动进入极准,极准仅在用户手动选择时启用。后端新增内存队列 <code>ANALYZE_QUEUE</code>,多个 <code>analyze</code> 请求按顺序执行;前端轮询所有运行中的 job不只轮询当前 active job。<code>VideoFramePanelNode</code> 内也加入同一套自动抽帧工具条。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。队列目前是进程内队列,重启后不会恢复未执行的排队任务。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 抽帧工具条降噪并修复追加失败</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Bugfix</span>
</header>
<div class="body">
<p><strong>问题:</strong>每个缩略图上方同时放目标、张数、精度和按钮太拥挤;另外追加抽帧时可能没有新增图片。</p>
<p><strong>根因:</strong>追加模式下 <code>frames</code> 目录已经存在,但后端仍使用非 <code>exist_ok</code><code>mkdir</code>,触发 <code>File exists</code> 后任务进入解析失败。</p>
<p><strong>改动:</strong>工具条默认只显示目标、抽帧/追加和设置按钮;张数、精度折叠到设置里。后端改为允许已存在的 <code>frames</code> 目录,追加模式不再因目录存在失败。缩略图高度增大到 192px横屏/竖屏都按真实比例显示;抽帧评分也按视频原比例缩放,不固定 16:9。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。已用临时 job 验证 append已有 1 张关键帧时追加 3 张后变为 4 张。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 自动抽帧增加本地精度模式</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Performance</span>
</header>
<div class="body">
<p><strong>问题:</strong>当前抽帧默认偏轻量,用户机器是 Apple M2 Max、12 核 CPU、38 核 GPU、64GB 内存,足以承受更密集的本地候选扫描;但需要在 UI 里提示算力档位,避免长视频误用重模式。</p>
<p><strong>改动:</strong>后端 <code>/jobs/{id}/analyze</code> 新增 <code>quality</code> 参数:快速为 2fps/360px精细为 8fps/720px极准为 12fps/960px高精度模式还会提高本地评分图分辨率保留竖屏比例不再把竖屏压扁到 16:9。每个缩略图工具条新增精度选择。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。实测本机 64.5s、1080×1920、60fps 视频12fps/960px 扫描 774 张候选约 2.61s;重型 ffmpeg 场景/模糊滤镜约 21.63s,因此继续使用本地 PIL/NumPy 评分更划算。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 每个输入视频缩略图绑定自己的抽帧工具条</h3>
<span class="tag violet">Input</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>统一放在缩略图浮条上方的抽帧工具条仍然不够明确,用户无法一眼判断当前会抽哪一个视频。</p>
<p><strong>改动:</strong>抽帧目标、张数、精度和抽帧按钮改为渲染在每个视频缩略图正上方,并且每个 job 独立保存目标、张数和精度设置。点击某个缩略图上方的抽帧按钮时,前端直接把该 <code>jobId</code> 传给 <code>analyzeJob</code>,同时切换 active job 并进入该 job 的进度轮询。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后续与输入视频相关的快捷操作都应优先贴近对应缩略图,不再依赖全局当前选择的心智。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 自动抽帧快捷工具条移到缩略图上方</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Frame Target</span>
</header>
<div class="body">
<p><strong>问题:</strong>自动抽帧入口放在 Input 卡片正文里,离视频缩略图和预览工作区较远;用户需要在看缩略图时快速切目标、切张数并反复抽取。</p>
<p><strong>改动:</strong>输入视频缩略图浮条上方新增自动抽帧快捷工具条,包含抽帧目标、张数快捷项和抽帧按钮。前端新增 <code>frameCount</code> 状态并把目标 / 张数传给 <code>analyzeJob</code>;已有关键帧时默认用 <code>mode=append</code> 追加抽取。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后端追加模式会保留已有关键帧,避开非常接近的时间点,并用新的 frame index 落盘。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 自动抽帧支持目标化扫描</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Frame Target</span>
</header>
<div class="body">
<p><strong>问题:</strong>单一“自动抽帧”无法表达这次要清晰人物、下次要转场变化或表情瞬间的不同目标;但把抽帧做成复杂参数面板会破坏 Input 卡片的轻量工作流。</p>
<p><strong>改动:</strong>Input 节点新增抽帧目标,默认“综合关键帧”,可切换清晰主体、转场变化、表情瞬间、动作峰值。后端 <code>/jobs/{id}/analyze</code> 新增 <code>target</code> 参数先低清低帧率扫描候选再按目标评分、pHash 去重、时序分桶,最后只对选中的时间点从原视频抽高质量关键帧。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。当前“人物/动物表情”是本地近似评分,后续可把候选小图接入视觉模型重排。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 视频抽帧面板默认静音播放</h3>
<span class="tag violet">Input</span>
<span class="tag blue">Video</span>
</header>
<div class="body">
<p><strong>问题:</strong>输入视频缩略图改为单击打开抽帧面板后,面板播放器如果自动播放带声音,会打断工作流。</p>
<p><strong>改动:</strong><code>VideoFramePanelNode</code> 的主播放器增加 <code>muted</code>,默认静音自动播放;用户仍可通过浏览器视频 controls 自行取消静音。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · Canvas Panel 颜色跟随来源卡片且视频单击打开</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>视频抽帧面板和画面工作台详情面板框架一致后,标题栏颜色也需要分别跟各自来源卡片一致;输入视频缩略图仍需要双击才能打开抽帧面板,操作偏重。</p>
<p><strong>改动:</strong>视频抽帧面板标题栏使用 <code>--grad-input</code>,画面工作台详情面板标题栏使用 <code>--grad-ai</code>输入视频缩略图改为单击打开抽帧面板hover 仍负责尺寸预览。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后续面板颜色应从来源卡片类型继承。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 画面工作台详情面板统一为 Canvas Panel 框架</h3>
<span class="tag violet">Visual Lab</span>
<span class="tag blue">Canvas Panel</span>
</header>
<div class="body">
<p><strong>问题:</strong>关键帧详情 / 元素提取面板虽然默认左侧吸附,但外层仍是旧橙色“钉住”框架,和视频抽帧面板的紫色标题栏、画布/左/右/底吸附按钮不一致。</p>
<p><strong>改动:</strong><code>KeyframePanelNode</code> 改为和 <code>VideoFramePanelNode</code> 同一套 Canvas Panel 外壳:紫色标题栏、画布模式、吸附左侧、吸附右侧、吸附底部、缩放、关闭和右下角拖拽缩放。面板定位状态从单一 <code>pinned</code> 语义升级为 <code>framePanelDock</code></p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后续工作面板应复用这套 Canvas Panel 框架。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 关键帧详情嵌入态去掉双标题栏</h3>
<span class="tag violet">Visual Lab</span>
<span class="tag blue">FrameLightbox</span>
</header>
<div class="body">
<p><strong>问题:</strong>画面工作台详情面板外层已有“关键帧详情 · 元素提取”工具栏,内层 <code>FrameLightbox</code> 嵌入态又显示一条橙红分镜导航栏,视觉上像两个窗口叠在一起。</p>
<p><strong>改动:</strong><code>FrameLightbox</code><code>embedded</code> 模式下不再渲染自己的顶部工具栏和外边框;上一帧 / 下一帧导航移动到 <code>KeyframePanelNode</code> 外层标题栏。</p>
<p><strong>影响:</strong><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。非嵌入式 lightbox 保留原标题栏。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 画面工作台详情面板默认左侧吸附</h3>
<span class="tag violet">Visual Lab</span>
<span class="tag blue">Dock</span>
</header>
<div class="body">
<p><strong>问题:</strong>画面工作台点击关键帧后,关键帧详情 / 元素提取面板仍默认作为画布节点出现,还会触发 <code>fitView</code> 拉动画布;和视频抽帧面板默认左侧吸附的规则不一致。</p>
<p><strong>改动:</strong><code>framePanelDock</code> 初始值改为 <code>left</code>;从关闭状态打开关键帧详情时默认吸附左侧。若面板已打开,用户切回画布模式后继续切换关键帧,不会被强制吸回左侧;默认吸附状态下也不再触发画布 <code>fitView</code></p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>docs/source-analysis.html</code>。画面工作台处理面板现在与视频抽帧面板保持同一默认策略:先贴左侧,需要时再切回画布。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 视频抽帧面板默认左侧吸附</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">Dock</span>
</header>
<div class="body">
<p><strong>问题:</strong>视频抽帧面板默认以画布节点方式打开时,用户还要再点一次吸附左侧,和“处理面板先贴边、需要时再拖回画布”的工作习惯不一致。</p>
<p><strong>改动:</strong><code>videoPanelDock</code> 初始值改为 <code>left</code>;从关闭状态双击输入视频缩略图时默认吸附左侧。面板已打开后,用户手动切换到画布 / 右侧 / 底部不会被找回动作覆盖。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>docs/source-analysis.html</code>。后续同类处理面板应优先考虑“默认贴边,必要时切回画布”的交互。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 视频抽帧面板支持删除已抽关键帧</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">Frames</span>
</header>
<div class="body">
<p><strong>问题:</strong>视频抽帧面板只能新增关键帧,用户看到“已抽关键帧”后不能在同一工作上下文里清理误抽的帧。</p>
<p><strong>改动:</strong>已抽关键帧缩略图右上角增加删除按钮,点击后按 <code>jobId + frameIdx</code> 删除对应关键帧;删除期间显示小号 loading。图片类素材仍沿用快速删除策略不弹确认。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。视频抽帧面板不再依赖当前 active job 来决定删除目标。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 吸附工作面板贴近视口边缘</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">Dock</span>
</header>
<div class="body">
<p><strong>问题:</strong>视频抽帧面板吸附左侧 / 右侧时顶部仍留出明显空白;这不是实际板块遮挡,而是面板吸附样式里硬编码了 <code>top: 72</code> 和对应的高度预留。关键帧面板也保留了旧 storyboard 顶栏避让逻辑。</p>
<p><strong>改动:</strong>新增统一吸附边距常量,视频抽帧面板和关键帧详情面板吸附时都贴近视口边缘,仅保留 8px 安全边距;移除关键帧面板对旧 <code>data-storyboard-dock</code> / <code>data-storyboard-bar</code> 的避让查询。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后续画布工作面板的吸附语义统一为“贴边”,不是为顶部旧板块预留空间。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 输入视频双击改为画布抽帧面板</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">Input</span>
</header>
<div class="body">
<p><strong>问题:</strong>输入视频缩略图双击原来只在 Input 节点上方展开一个临时播放器,不能作为无限画布工作台移动、找回或吸附,后续其他板块也无法复用这种交互。</p>
<p><strong>改动:</strong>新增 <code>videoFramePanel</code> ReactFlow 节点和 <code>VideoFramePanelNode</code>。双击输入视频缩略图会打开/找回画布内抽帧面板,面板可拖动、右下角缩放,也可一键吸附到左侧、右侧或底部边缘;面板内支持播放、时间轴定位、当前时间抽帧和查看已抽关键帧。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。原固定全屏 <code>VideoLightbox</code> 不再从 Input 双击路径进入;后续处理板块应复用同类画布工作面板语义。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · Hover 大预览尺寸信息增强</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">HoverPreview</span>
</header>
<div class="body">
<p><strong>问题:</strong>原始尺寸和 Fit 比例之前是左上角小号单行文字,用户在快速扫缩略图时不够醒目,无法马上感知素材分辨率差异。</p>
<p><strong>改动:</strong><code>HoverPreview</code> 左上角改为两层信息徽章:标题固定为“原始尺寸”,主数字用大号等宽字体显示 <code>×</code>,旁边保留 <code>Fit</code> 百分比或 <code>1:1 原寸</code> 状态。</p>
<p><strong>影响:</strong><code>web/components/nodes/hover-preview.tsx</code><code>docs/source-analysis.html</code>。定位和 Fit / 1:1 行为不变,只提升尺寸信息可读性。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 删除确认改为页面内分层交互</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">UX</span>
</header>
<div class="body">
<p><strong>问题:</strong>浏览器原生删除确认会突然出现在页面顶部,和无限画布的操作上下文割裂;图片类素材如果每次删除都确认,也会拖慢快速整理素材的节奏。</p>
<p><strong>改动:</strong>输入视频和生成视频任务删除改为画布内确认层,背景轻遮罩并支持点击背景 / Esc 取消;关键帧和元素提取图属于可快速整理的图片素材,点击删除后直接执行。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。视频删除仍走既有删除接口;图片删除仍走原有回调,只调整确认策略。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 输入视频缩略图支持删除整个 job</h3>
<span class="tag violet">Input</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>画布顶部输入视频缩略图只有切换和预览,没有删除入口;用户清理视频队列时只能删关键帧或生成视频任务,不能删除整个输入视频。</p>
<p><strong>改动:</strong>新增 <code>DELETE /jobs/{id}</code>,前端新增 <code>deleteJob</code><code>onDeleteJob</code>Input 缩略图右上角常驻删除按钮,确认后移除该 job、清理 URL 参数,并删除本地 <code>jobs/&lt;id&gt;</code> 目录。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 大图预览改为尺寸感知 Fit / 1:1</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">HoverPreview</span>
</header>
<div class="body">
<p><strong>问题:</strong>有些源视频和图片分辨率很大,完全按原尺寸展示会撑爆画布;但统一固定预览大小又会丢失素材真实尺寸感知。</p>
<p><strong>改动:</strong><code>HoverPreview</code> 默认按原比例适应可视区域,角标显示原始分辨率和当前 Fit 百分比;点击钉住后可切换到 <code>1:1</code>,超大素材在预览框内滚动查看局部。</p>
<p><strong>影响:</strong><code>web/components/nodes/hover-preview.tsx</code>。视觉缩略图高度同步放大,图片缩略图的复制 / 删除按钮改为常驻大号 icon。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 缩略图滑动条改为大号可拖轨道</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">Thumbnail</span>
</header>
<div class="body">
<p><strong>问题:</strong>节点上方缩略图横排内容多时,原生横向滚动条太细且在画布缩放下不容易点中,用户很难拖动。</p>
<p><strong>改动:</strong>新增 <code>FloatingThumbnailStrip</code> / <code>ThumbnailScrollRail</code>,在缩略图下方显示明显的大号紫色拖动轨道;轨道支持点击跳转、按住拖动和键盘左右移动,并用 <code>nodrag nopan</code> 避免误触发画布拖拽。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code>。Input、VisualLab 以及保留的旧视觉节点缩略图条共用同一交互。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 三个视觉节点合并为画面工作台</h3>
<span class="tag violet">VisualLab</span>
<span class="tag blue">Canvas</span>
</header>
<div class="body">
<p><strong>问题:</strong>镜头拆解、元素改造、生成视频三个卡片在主画布上占同等权重,但它们只是视觉素材状态展示和入口;真正处理都在点击后的工作台/面板中完成。</p>
<p><strong>改动:</strong>新增 <code>VisualLabNode</code>,把关键帧、元素 cutout、视频任务缩略图合并到一个“画面工作台 · Visual Lab”卡片里DAG 从 <code>Input → Keyframe → Storyboard → VideoGen → Compose</code> 简化为 <code>Input → VisualLab → Compose</code>,音频线仍独立。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。底层数据、接口和旧节点组件暂不删除。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 修复节点右下角缩放点击偏移</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">Resize</span>
</header>
<div class="body">
<p><strong>问题:</strong>点击或轻微拖动卡片右下角缩放把手时,节点会突然偏移/跳变,影响在无限画布上精调卡片大小。</p>
<p><strong>原因:</strong><code>getComputedStyle(nodeEl).width/height</code> 读到的已经是 ReactFlow 节点坐标下的布局尺寸,旧逻辑又除了一次 <code>zoom</code>,导致拖动起始宽高被放大或缩小;点击时 1px 级 pointermove 也会立刻写入错误宽高。</p>
<p><strong>改动:</strong>起始宽高直接使用 computed style只有鼠标移动量按 zoom 换算;增加 2px 点击死区,单纯点击缩放角不再改写节点尺寸。</p>
<p><strong>影响:</strong><code>web/components/nodes/resize-handle.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 缩略图 hover 原尺寸预览贴缩略图上边缘</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">HoverPreview</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户要的是“鼠标停在卡片缩略图上时,原尺寸图片/视频在该缩略图上方边缘展示,并且仍属于无限画布”;不能贴节点卡片上边缘,也不能放到页面 fixed 层。</p>
<p><strong>改动:</strong><code>HoverPreview</code> 增加缩略图锚点坐标,预览层仍渲染在 ReactFlow 节点 DOM 内,但用缩略图的中心 x 和 top y 定位预览底边贴缩略图上边缘Input 视频、镜头拆解关键帧、元素改造 cutout、生成视频缩略图统一走该逻辑。</p>
<p><strong>影响:</strong><code>web/components/nodes/hover-preview.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 打开应用自动恢复历史 job</h3>
<span class="tag blue">API</span>
<span class="tag violet">Page</span>
</header>
<div class="body">
<p><strong>问题:</strong>前端只从 URL <code>?job=</code> 读 job 列表,没有任何本地或后端列表回填,打开 <code>/</code> 不带参数就是空白,之前跑过的 job 看不见。</p>
<p><strong>改动:</strong>后端新增 <code>GET /jobs</code> 列表接口(返回 <code>JobSummary</code>id/url/status/progress/duration/width/height/video_url/frame_count/video_count/thumbnail/error/mtime按 state.json mtime 倒序,可带 <code>limit</code>)。前端 <code>page.tsx</code> 启动逻辑URL 有 <code>?job=</code> 时尊重 URL没有时自动调 <code>listJobs()</code> 拿全部历史,反转后让最新 job 落在末尾active。持久化基于 <code>api/jobs/&lt;id&gt;/state.json</code> 磁盘文件,不依赖浏览器存储,换浏览器/清缓存都不会傻。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>JobSummary</code> 类型 + <code>list_jobs</code> endpoint<code>web/lib/api.ts</code><code>JobSummary</code> + <code>listJobs</code>)、<code>web/app/page.tsx</code>(启动 useEffect</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 允许骨骼人使用按摩仪后状态变好</h3>
<span class="tag blue">Prompt</span>
</header>
<div class="body">
<p><strong>问题:</strong>本轮参考素材的主体可能是人形骷髅/透明骨骼人,不应被提示词强行替换成普通真人;核心是“用了 SKG 颈部按摩仪后状态变好”。</p>
<p><strong>改动:</strong>prompt 允许保留人形骷髅作为视觉隐喻,要求正确佩戴 U 形颈部按摩仪,并通过从僵硬/揉脖子/疲惫到抬头/肩颈舒展/放松来表现状态改善;同时禁止恐怖化、血腥化或夸大医疗治愈。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频提示词改为产品图优先</h3>
<span class="tag blue">Prompt</span>
<span class="tag violet">StoryboardWorkbench</span>
</header>
<div class="body">
<p><strong>问题:</strong>已经有真实 SKG 产品图后,提示词还偏“借鉴原视频”,容易让模型保留竞品或改变产品形态。</p>
<p><strong>改动:</strong>生成视频 prompt 明确“上传的 SKG 产品图是唯一产品真源”,首尾帧只控制起止构图、场景和动作,原视频链接只参考节奏和镜头调度;增加产品一致性、产品呈现、禁止非 SKG 产品/竞品包装/随机新增结构等约束。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 产品参考支持本地上传和拖拽</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>SKG 产品参考不能只依赖从关键帧/元素里复制,用户需要手动上传同一产品的多角度图。</p>
<p><strong>改动:</strong><code>SKG 产品参考</code> 区支持点击上传和拖拽上传图片,一次可传多张,最多保留 6 张。后端新增 <code>POST /jobs/{job_id}/assets</code> 保存产品图资产,并返回 <code>kind: "asset"</code><code>ImageRef</code>;生成视频时这些资产会作为产品参考传入 Ark。</p>
<p><strong>影响:</strong><code>web/components/storyboard-workbench.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 首尾帧编排增加 SKG 产品槽</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>首尾帧可以控制视频起止,但还需要单独指定 SKG 产品图,避免模型只模仿原视频动作而没有稳定产品外观。</p>
<p><strong>改动:</strong>分镜编排区新增 <code>SKG 产品</code> 槽,和首帧、尾帧并列;生成视频时把该槽作为 <code>product_image</code> 提交。Ark 请求会附加一张 <code>reference_image</code> 产品参考图;如果接口不接受额外参考图,后端自动回退到首尾帧生成。</p>
<p><strong>影响:</strong><code>web/components/storyboard-workbench.tsx</code><code>web/app/page.tsx</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜编排改为首尾帧生成</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>赶交付时顶部横向分镜缩略条占空间4 图槽也不如“首帧到尾帧”直接;用户希望直接做首尾帧视频生成。</p>
<p><strong>改动:</strong>移除 <code>StoryboardBar</code> 的横向分镜缩略图区域,只保留标题栏和展开按钮;<code>StoryboardWorkbench</code> 改成首帧 / 尾帧两个槽,首帧默认当前分镜,尾帧默认下一张已选分镜,也可从剪贴板粘贴指定结束画面。后端 <code>/storyboard/video</code> 支持 <code>first_image</code>/<code>last_image</code>Ark 请求同时传 <code>first_frame</code>/<code>last_frame</code>,如果接口不接受尾帧字段则自动回退到单首帧。</p>
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code><code>web/components/storyboard-workbench.tsx</code><code>web/app/page.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频携带原视频链接做节奏参考</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户赶交付,希望直接把上传的原视频链接给视频模型参考,而不是只靠单张关键帧。</p>
<p><strong>改动:</strong>前端提交生视频时增加 <code>source_ref: { kind: "source_video", url: job.url }</code>Ark 请求体在文本 prompt 和首帧之外追加 <code>video_url</code> 参考视频,用于模仿节奏、镜头运动和动作顺序。如果 Ark 返回 400/422 不接受参考视频字段,后端自动回退到“当前关键帧首帧生成”,保证这次不会直接阻断出片。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 快速出片改为关键帧直生视频</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">Prompt</span>
</header>
<div class="body">
<p><strong>问题:</strong>赶交付时不适合再让 4 图槽决定首帧;如果某个槽里是抠图元素,模型会拿碎元素当第一帧,视频容易不连贯。</p>
<p><strong>改动:</strong>“生成视频”按钮改成直接用当前分镜关键帧作为首帧提交4 图槽和改造目标只作为提示词参考;提示词强调一镜到底、首帧稳定、时间线连续、禁止跳切/换场景/主体变形。后端取关键帧时优先使用未应用的清洗版,否则使用当前 frame 文件。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/storyboard-workbench.tsx</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频支持火山方舟 Ark 异步任务</h3>
<span class="tag rose">VideoGenNode</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户提供火山方舟 <code>https://ark.cn-beijing.volces.com/api/v3</code> 作为生视频通道;这个通道不是 Poe 的 <code>/videos</code> 形态,而是内容生成异步任务。</p>
<p><strong>改动:</strong>后端识别 Ark base 后,提交改为内容生成任务接口,火山方舟直连使用 <code>POST /contents/generations/tasks</code>SKG 豆包网关使用 <code>POST /api/v3/contents/generations/tasks</code>。请求体使用 <code>content</code> 数组:文本 prompt + 首帧 <code>image_url</code> data URL轮询对应的 <code>{id}</code> 任务地址,成功后读取 <code>content.video_url</code> 下载 MP4。本机默认 Seedance 模型改为 Ark 可见的 <code>doubao-seedance-2-0-fast-260128</code></p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>docs/source-analysis.html</code>。本机 <code>api/.env</code> 需要把 <code>VIDEO_API_BASE_URL</code>/<code>VIDEO_API_KEY</code>/<code>VIDEO_CREATE_PATHS</code>/<code>VIDEO_STATUS_PATH</code> 指向 Ark。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频改接 Poe 视频模型</h3>
<span class="tag rose">VideoGenNode</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>SKG ezlink 的 OpenAI 兼容 base 可列出部分模型,但常规 <code>/videos</code> 入口返回 404/unsupported用户确认可用的视频模型在 Poe 通道里。</p>
<p><strong>改动:</strong>后端新增 <code>POE_API_BASE_URL</code>/<code>POE_API_KEY</code> 配置,未显式配置 <code>VIDEO_API_BASE_URL</code> 时优先走 PoeSeedance / Kling / Veo/Voe 业务别名默认映射到 Poe 真实模型 <code>seedance-2-fast</code><code>kling-omni</code><code>veo-3.1-fast</code>。Poe 提交使用 <code>input_image</code> base64继续轮询 <code>/videos/{id}</code> 并下载 <code>/videos/{id}/content</code></p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>docs/source-analysis.html</code>。密钥只放本地 <code>api/.env</code>,不进入源码解析页。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频提交不再被前端锁死</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>虽然当前探测到常见视频入口返回 404/unsupported但模型层确实有视频模型不能在前端简单判定“未开通”并禁用。</p>
<p><strong>改动:</strong>撤掉分镜编排里的前置禁用;后端允许提交 seedance / kling / veo / voe并支持通过 <code>VIDEO_CREATE_PATHS</code> 逗号分隔配置多个候选生成入口,逐个尝试。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>web/app/page.tsx</code><code>web/components/storyboard-workbench.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 生视频错误提示改为可读原因</h3>
<span class="tag rose">VideoGenNode</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>提交生视频失败时,前端把 <code>generateStoryboardVideo 503 {"detail": ...}</code> 原样展示,用户无法快速判断是配置、端点还是 UI 问题。</p>
<p><strong>改动:</strong><code>generateStoryboardVideo</code> 解析后端 JSON 的 <code>detail</code> 后再抛错后端错误文案区分“模型存在”和“入口不可用”Video Gen 失败卡把 <code>/videos 404</code> 长错误压缩成一句可读原因。</p>
<p><strong>影响:</strong><code>web/lib/api.ts</code><code>web/components/nodes/index.tsx</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · Video Gen 卡片增加复制和删除</h3>
<span class="tag rose">VideoGenNode</span>
<span class="tag blue">API</span>
</header>
<div class="body">
<p><strong>问题:</strong>Video Gen 节点上方失败/完成任务卡只有整卡点击复制,不够明确;失败任务也无法从界面清掉。</p>
<p><strong>改动:</strong>每张视频任务卡左上角增加复制 prompt 按钮,右上角增加删除任务按钮;后端新增 <code>DELETE /jobs/{job_id}/storyboard-videos/{video_id}</code>,删除 <code>generated_videos</code> 记录并清理本地任务目录。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/app/page.tsx</code><code>web/lib/api.ts</code><code>api/main.py</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜编排接入真实生视频任务</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag rose">VideoGenNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>4 图槽已经粘贴参考图后,用户要直接调用生视频 API而不是只生成 prompt 或图片任务。</p>
<p><strong>改动:</strong>分镜编排明细区增加 Seedance / Kling / Veo 3 模型选择和“调用模型生成视频”按钮;后端新增 <code>/jobs/{job_id}/frames/{idx}/storyboard/video</code>。提交后按 <code>VIDEO_CREATE_PATHS</code> 逐个尝试生成入口,成功后轮询并保存 MP4失败时保留任务卡和具体入口错误方便继续排查网关实际路径。<code>VideoGenNode</code> 读取 <code>job.generated_videos</code> 展示排队、生成中、失败和完成视频。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>web/components/storyboard-workbench.tsx</code><code>web/components/nodes/index.tsx</code><code>web/app/page.tsx</code><code>web/lib/api.ts</code>。Sora 不再作为默认模型;真实模型 ID 通过 <code>VIDEO_MODEL_SEEDANCE</code><code>VIDEO_MODEL_KLING</code><code>VIDEO_MODEL_VEO3</code> 配置,真实视频 API 地址通过 <code>VIDEO_API_BASE_URL</code>/<code>VIDEO_API_KEY</code> 配置。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜编排下拉区支持上推缩小</h3>
<span class="tag violet">StoryboardWorkbench</span>
</header>
<div class="body">
<p><strong>问题:</strong>分镜编排明细区默认占用太多顶部面积,展开后下方画布空间不足。</p>
<p><strong>改动:</strong>明细区默认高度降为 320px并增加底部拖拽手柄可上推缩到 180px也可下拉放大查看完整内容。</p>
<p><strong>影响:</strong><code>web/components/storyboard-workbench.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜缩略图条与编排明细合并为一个下拉区</h3>
<span class="tag violet">StoryboardBar</span>
<span class="tag violet">StoryboardWorkbench</span>
</header>
<div class="body">
<p><strong>问题:</strong>顶部分镜缩略图条和下方内嵌工作台都带分镜导航,看起来像两个不同板块。</p>
<p><strong>改动:</strong><code>StoryboardBar</code> 成为唯一分镜导航;<code>StoryboardWorkbench</code> 移除自己的标题栏、左侧分镜列表和底部快捷栏,只保留当前分镜的 4 图槽与改造目标明细。</p>
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code><code>web/components/storyboard-workbench.tsx</code><code>web/app/page.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜头编排工作台改为内嵌下拉</h3>
<span class="tag violet">StoryboardWorkbench</span>
<span class="tag violet">StoryboardBar</span>
</header>
<div class="body">
<p><strong>问题:</strong>元素改造节点等入口仍会打开 <code>fixed inset-0</code> 的全屏 <code>StoryboardWorkbench</code>,用户感觉像跳转页面。</p>
<p><strong>改动:</strong>移除 <code>StoryboardWorkbench</code> 的 portal 全屏承载方式,改为渲染在顶部分镜栏下方;所有“打开编排”入口只展开这个内嵌区域。</p>
<p><strong>影响:</strong><code>web/components/storyboard-workbench.tsx</code><code>web/components/storyboard-bar.tsx</code><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 钉住面板停靠到分镜头编排边缘</h3>
<span class="tag orange">KeyframePanelNode</span>
<span class="tag violet">StoryboardBar</span>
</header>
<div class="body">
<p><strong>问题:</strong>关键帧详情钉住在浏览器左侧固定位置时,会遮挡顶部分镜头编排栏展开后的缩略图区域。</p>
<p><strong>改动:</strong><code>StoryboardBar</code> 增加稳定 DOM 标记;钉住面板实时读取该区域下边缘,并吸附到其下方。展开 / 折叠分镜头编排时,钉住面板自动让位。</p>
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code><code>web/components/nodes/index.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 顶部分镜头编排不再跳转全屏工作台</h3>
<span class="tag violet">StoryboardBar</span>
</header>
<div class="body">
<p><strong>问题:</strong>顶部 <code>StoryboardBar</code> 的“进入编排”和分镜缩略图点击会打开全屏 <code>StoryboardWorkbench</code>,打断当前画布流程。</p>
<p><strong>改动:</strong>顶部按钮改为“展开编排”,只下拉展示当前分镜列表;缩略图点击只聚焦该分镜,不再触发全屏跳转。后续已把工作台整体改成内嵌下拉,见上方最新记录。</p>
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code><code>web/app/page.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 钉住关键帧详情改为左侧停靠</h3>
<span class="tag orange">KeyframePanelNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>钉住后仍像自由浮层一样停在画布附近,用户继续缩放画布或调整面板尺寸时容易把它和画布节点混在一起。</p>
<p><strong>改动:</strong>钉住后统一吸附到浏览器左侧边缘,脱离 ReactFlow 画布缩放;钉住瞬间把当前可见大小转换成面板真实尺寸,之后只由右下角拖拽或标题栏按钮调整。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>;钉住语义从“原地浮在上层”改为“左侧停靠工作面板”。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 关键帧详情支持右下角拖拽缩放和上层钉住</h3>
<span class="tag orange">KeyframePanelNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>只有按钮缩放不够直观;钉住后仍作为画布节点,会继续随 ReactFlow 画布缩放。</p>
<p><strong>改动:</strong>增加右下角拖拽缩放手柄;钉住时通过 portal 固定到浏览器上层,脱离 ReactFlow 画布缩放和平移。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/app/page.tsx</code>;未钉住时仍是画布节点,钉住后保持屏幕固定位置。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 关键帧详情面板增加钉住按钮</h3>
<span class="tag orange">KeyframePanelNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>面板可以拖动后,用户仍可能误拖;切换图片时希望保持固定工作位置。</p>
<p><strong>改动:</strong>在标题栏增加钉子按钮。钉住后面板节点禁止拖动,切换关键帧只切换内容不移动位置;取消钉住后可继续拖动。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 切换关键帧不再重置详情面板位置</h3>
<span class="tag orange">KeyframePanelNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>用户把关键帧详情面板拖到合适位置后,再点击下一张关键帧会把面板拉回默认位置,造成视觉疲劳。</p>
<p><strong>改动:</strong>已打开的面板只切换内容,不移动位置;只有面板不存在、首次打开时才放到默认位置并自动聚焦。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code>;关闭后重新打开仍会出现在默认位置。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 关键帧详情面板增加缩放控制</h3>
<span class="tag orange">KeyframePanelNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>关键帧详情面板作为画布节点后可以随画布缩放,但面板自身没有尺寸控制,用户无法单独放大或缩小它。</p>
<p><strong>改动:</strong>在面板标题栏增加 <code>-</code>、百分比重置、<code>+</code> 控制,支持 75% 到 135% 的面板级缩放。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code>;点击新关键帧仍会找回到默认位置,缩放比例保留。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 关键帧详情从固定左侧抽屉迁到无限画布</h3>
<span class="tag orange">KeyframeNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>关键帧详情 / 元素提取面板固定在左侧 drawer和 ReactFlow 无限画布割裂,也不会跟随画布缩放。</p>
<p><strong>改动:</strong>移除主页面隐藏渲染的 <code>Dashboard</code> drawer 承载方式,新增独立 <code>keyframePanel</code> ReactFlow 节点来挂载 <code>FrameLightbox</code></p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code>;点关键帧后面板默认出现在流程左侧空白画布里,不遮挡 Input / Keyframe 主节点;标题栏可拖动,跟随 ReactFlow 平移和缩放。再次点击关键帧缩略图会把面板找回到默认位置,并自动把视野拉到“关键帧 + 面板”。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 元素改造 hover 预览简化为原帧预览</h3>
<span class="tag violet">StoryboardNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>元素改造节点的 hover 预览虽然已改为节点内显示,但仍比关键帧节点复杂,多了“来源原帧 / 提取元素”两栏和元素名称,信息过载。</p>
<p><strong>改动:</strong>改成和镜头拆解关键帧一致的简单预览:只显示来源原帧,底部显示分镜编号和时间。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>;元素改造板块 hover 现在更轻,不干扰当前判断。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 元素改造 hover 预览改为节点内效果</h3>
<span class="tag violet">StoryboardNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>上一版把元素预览用 <code>createPortal</code> 挂到 <code>body</code>DevTools 里会出现额外 fixed 层,交互形态和关键帧节点不一致。</p>
<p><strong>改动:</strong>改成节点内 <code>group-hover</code> 预览,不再向 <code>body</code> 插入预览层。后续又简化为只展示来源原帧,见上方最新记录。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>;元素改造板块的 DOM 和交互效果更接近关键帧缩略图。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 新增独立源码解析与协作地图</h3>
<span class="tag blue">docs</span>
</header>
<div class="body">
<p><strong>目的:</strong>把产品功能区、源码位置、接口、数据模型、需求描述方式固定下来,减少“描述不准导致改偏”。</p>
<p><strong>影响:</strong>新增 <code>docs/source-analysis.html</code>,不接入 Next 应用,不影响工作台运行。</p>
<p><strong>以后描述:</strong>可以直接引用本页的功能区名称、节点职责和源码文件名。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · Storyboard 元素缩略图 hover 预览修复</h3>
<span class="tag violet">StoryboardNode</span>
</header>
<div class="body">
<p><strong>问题:</strong>元素改造节点上方小图 hover 没有像镜头拆解节点一样显示原图预览,并且首次修复时出现运行时错误。</p>
<p><strong>原因:</strong>节点内部 overflow 裁剪了预览;随后 portal 预览里把变量写成了不存在的 <code>aspectRatio</code></p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>。该记录之后又改为节点内预览,见上方最新记录。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 元素识别结果不再锁死</h3>
<span class="tag orange">元素提取</span>
</header>
<div class="body">
<p><strong>问题:</strong>Vision 识别可能错,但元素列表像最终结果;点击提取图会跳页面,打断用户思路。</p>
<p><strong>改动:</strong>支持元素改名、改英文提示、改位置、删除元素、重复提取、删除单张提取图;提取图不再用链接跳新页。</p>
<p><strong>影响:</strong><code>FrameLightbox</code><code>web/lib/api.ts</code><code>PATCH /jobs/{job_id}/frames/{idx}/elements/{element_id}</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 分镜编排入口聚焦到具体分镜</h3>
<span class="tag violet">StoryboardWorkbench</span>
</header>
<div class="body">
<p><strong>问题:</strong>从 Storyboard 或顶部分镜条进入编排时,没有明确定位到用户正在看的那一帧。</p>
<p><strong>改动:</strong>工作台接受 <code>focusedFrame</code>,点击缩略图会打开工作台并聚焦对应分镜。</p>
<p><strong>影响:</strong><code>page.tsx</code><code>StoryboardBar</code><code>StoryboardWorkbench</code><code>StoryboardNode</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-13 · 视觉管线不再被 ASR 阻断</h3>
<span class="tag green">Pipeline</span>
</header>
<div class="body">
<p><strong>问题:</strong>SKG 网关 audio 不通时,视觉解析也容易被标记失败。</p>
<p><strong>改动:</strong><code>analyze</code> 主流程强调拆轨和关键帧,声音文案轨独立处理。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>page.tsx</code>、节点语义说明。</p>
</div>
</article>
</div>
</section>
<section id="update-rule" data-search>
<h2>更新规则</h2>
<div class="callout">
<p>以后任何改动只要影响产品理解、节点职责、界面行为、数据模型、API、运行方式或用户操作路径都要同步更新本页的对应章节和“变更记录”。</p>
</div>
<table style="margin-top:14px">
<thead>
<tr><th>改动类型</th><th>必须更新本页哪里</th><th>原因</th></tr>
</thead>
<tbody>
<tr><td>节点文案 / 节点功能</td><td>业务管线、节点职责边界、界面区域到源码、变更记录</td><td>避免用户按旧节点理解描述需求。</td></tr>
<tr><td>新增 / 修改接口</td><td>接口地图、数据模型、变更记录</td><td>避免前后端契约不清。</td></tr>
<tr><td>新增数据字段</td><td>数据模型、源码结构地图、变更记录</td><td>刷新恢复和 state.json 依赖字段一致。</td></tr>
<tr><td>改交互路径</td><td>界面区域到源码、需求描述模板、变更记录</td><td>用户描述“点击哪里”时必须和真实路径一致。</td></tr>
<tr><td>修 bug</td><td>变更记录;如果暴露新坑,也更新当前已通与阻塞</td><td>让后续同类问题能快速定位。</td></tr>
</tbody>
</table>
</section>
</main>
</div>
<script>
const input = document.getElementById("search");
const searchable = Array.from(document.querySelectorAll("[data-search]"));
input.addEventListener("input", () => {
const q = input.value.trim().toLowerCase();
searchable.forEach((el) => {
const text = el.innerText.toLowerCase();
el.classList.toggle("hidden-by-search", q && !text.includes(q));
});
});
</script>
</body>
</html>