auto-save 2026-05-14 06:38 (~3)
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 1,
|
||||
"hash": "440164e",
|
||||
"message": "auto-save 2026-05-12 17:50 (~1)",
|
||||
"ts": "2026-05-12T17:51:03+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 2,
|
||||
"hash": "aa5ad08",
|
||||
"message": "auto-save 2026-05-12 18:29 (+1, ~1)",
|
||||
"ts": "2026-05-12T18:29:59+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 3,
|
||||
"hash": "64db093",
|
||||
@@ -3348,6 +3334,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 5 项未提交变更 · 最近提交:auto-save 2026-05-14 06:27 (~4)",
|
||||
"files_changed": 5
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-14T06:33:37+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-14 06:33 (~5)",
|
||||
"hash": "0d86b4c",
|
||||
"files_changed": 5
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T22:38:51Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 3 项未提交变更 · 最近提交:auto-save 2026-05-14 06:33 (~5)",
|
||||
"files_changed": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -623,7 +623,7 @@ api/main.py
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
<div><strong>你看到的区域</strong><span>关键帧素材审核面板</span></div>
|
||||
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、审核”四个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部参考帧网格;右侧承载当前页操作、状态和结果;主体资产页只确认一个统一主体,默认用全部关键帧或已选关键帧作为参考,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code>、<code>addElement</code>、<code>generateSubjectAssets</code>、<code>generateSceneAsset</code>。</span></div>
|
||||
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、审核”四个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考;右侧承载当前页操作、状态和结果。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt,再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code>、<code>addElement</code>、<code>generateSubjectAssets</code>、<code>generateSceneAsset</code>。</span></div>
|
||||
<div><strong>适合怎么描述</strong><span>“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图和质量风险应该如何审核”。</span></div>
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
@@ -731,7 +731,7 @@ SubjectAsset {
|
||||
<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>scene_mode</code> 和 <code>scene_style</code>,可做原场景补背景、相似新场景或同构换风格,保留历史版本用于人工审核。</td></tr>
|
||||
<tr><td>场景资产</td><td><code>POST /frames/{idx}/scene-asset</code></td><td><code>generateSceneAsset</code></td><td>在统一主体资产之后,按当前关键帧生成去主体背景板;请求包含 <code>scene_mode</code>、<code>scene_style</code>、<code>prompt</code> 和 <code>source_frame_indices</code>,可用左侧选择的参考帧 + 右侧关键词生成原场景补背景、相似新场景或同构换风格,保留历史版本用于人工审核。</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>
|
||||
@@ -841,6 +841,19 @@ SubjectAsset {
|
||||
<h2>变更记录</h2>
|
||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||
<div class="changelog">
|
||||
<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>
|
||||
@@ -849,7 +862,7 @@ SubjectAsset {
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>主体资产不是抠图,也不是只看当前单帧生成多角度;主体页需要看到全部参考帧,并用这些参考重新绘制一个完整主体。</p>
|
||||
<p><strong>改动:</strong><code>FrameLightbox</code> 在“主体资产”页左侧显示全部参考帧网格,小图排列,可点击切换当前帧;右侧仍负责统一主体确认和生成。人物/生物默认视图改为六张标准站立/转身图:正面、背面、左侧、右侧、左前 45°、右前 45°。</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>
|
||||
|
||||
@@ -42,6 +42,27 @@ const LIVING_VIEW_OPTIONS = [
|
||||
["three_quarter_right", "右前 45°"],
|
||||
]
|
||||
|
||||
const LIVING_EXPRESSION_OPTIONS = [
|
||||
["expression_neutral", "中性脸"],
|
||||
["expression_smile", "微笑"],
|
||||
["expression_happy", "开心"],
|
||||
["expression_serious", "严肃"],
|
||||
["expression_surprised", "惊讶"],
|
||||
]
|
||||
|
||||
const LIVING_ACTION_OPTIONS = [
|
||||
["action_walk", "走路"],
|
||||
["action_turn", "转身"],
|
||||
["action_hold", "手持"],
|
||||
["action_use", "使用"],
|
||||
]
|
||||
|
||||
const LIVING_VIEW_GROUPS = [
|
||||
{ title: "身份标准图", hint: "默认必出 · 用来锁定长相、体型、服装和比例", options: LIVING_VIEW_OPTIONS },
|
||||
{ title: "表情补充", hint: "需要口播、反应或情绪镜头时再勾", options: LIVING_EXPRESSION_OPTIONS },
|
||||
{ title: "动作补充", hint: "需要动作镜头时再勾,仍保持同一人物身份", options: LIVING_ACTION_OPTIONS },
|
||||
]
|
||||
|
||||
type LightboxTab = "clean" | "scene" | "subject" | "review"
|
||||
|
||||
const LIGHTBOX_TABS: Array<{ key: LightboxTab; label: string }> = [
|
||||
@@ -550,6 +571,8 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
className="flex flex-col items-stretch gap-2 overflow-y-auto pr-1"
|
||||
style={isSubjectTab
|
||||
? { flex: "1 1 360px", minWidth: 220, maxWidth: 460, minHeight: 0 }
|
||||
: isSceneTab
|
||||
? { flex: "1 1 430px", minWidth: 280, maxWidth: 560, minHeight: 0 }
|
||||
: isCleanTab
|
||||
? { flex: "1 1 500px", minWidth: 300, maxWidth: 600, minHeight: 0 }
|
||||
: { flex: "1 1 560px", minWidth: 300, maxWidth: 680, minHeight: 0 }}
|
||||
@@ -606,6 +629,76 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
这些参考帧会一起传给模型,用来重绘同一个主体;不是逐张抠图。
|
||||
</div>
|
||||
</section>
|
||||
) : isSceneTab ? (
|
||||
<section className="rounded-lg border border-emerald-300/15 bg-emerald-500/[0.06] p-2.5">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="text-[12px] font-semibold text-white">场景参考图</div>
|
||||
<span className="text-[9.5px] font-mono text-white/38">
|
||||
{selectedFrameIndices.length > 0 ? `${selectedFrameIndices.length} 已选参考` : "默认当前帧"}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="grid gap-2"
|
||||
style={{ gridTemplateColumns: "repeat(auto-fill, minmax(104px, 1fr))" }}
|
||||
>
|
||||
{frames.map((frame) => {
|
||||
const active = frame.index === f.index
|
||||
const checked = selected.has(frame.index)
|
||||
return (
|
||||
<div
|
||||
key={frame.index}
|
||||
className={`overflow-hidden rounded-md border bg-black/35 transition ${
|
||||
active
|
||||
? "border-emerald-300/70 shadow-[0_0_0_1px_rgba(110,231,183,0.22)]"
|
||||
: "border-white/10 hover:border-emerald-300/45"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(frame.index)}
|
||||
className="block w-full text-left"
|
||||
title={`设为生成目标:分镜 ${frame.index + 1}`}
|
||||
>
|
||||
<div className="relative aspect-[9/13] bg-black">
|
||||
<img
|
||||
src={referenceFrameSrc(frame)}
|
||||
alt={`scene reference ${frame.index}`}
|
||||
className="h-full w-full object-contain"
|
||||
draggable={false}
|
||||
/>
|
||||
<span className="absolute left-1 top-1 rounded bg-black/65 px-1 py-0.5 text-[8.5px] font-mono text-white/80">
|
||||
{String(frame.index + 1).padStart(2, "0")}
|
||||
</span>
|
||||
{active && (
|
||||
<span className="absolute right-1 top-1 rounded bg-emerald-500/80 px-1 py-0.5 text-[8px] text-white">
|
||||
目标
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
<div className="flex items-center justify-between gap-1 px-1.5 py-1 text-[9.5px] text-white/52">
|
||||
<span>{frame.timestamp.toFixed(2)}s</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggleSelect(frame.index)}
|
||||
className={`rounded px-1.5 py-0.5 transition ${
|
||||
checked
|
||||
? "bg-emerald-400/80 text-black"
|
||||
: "bg-white/10 text-white/55 hover:bg-white/18 hover:text-white"
|
||||
}`}
|
||||
title={checked ? "取消场景参考" : "加入场景参考"}
|
||||
>
|
||||
{checked ? "参考" : "选"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] leading-relaxed text-white/38">
|
||||
左侧显示全部关键帧;点图片设为生成目标,点“选”加入场景参考。未选择时默认只参考当前目标帧。
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<div
|
||||
ref={imgWrapRef}
|
||||
@@ -666,7 +759,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
{/* 右侧主体识别 + 主体资产 */}
|
||||
<div
|
||||
className="flex flex-col gap-2.5 overflow-y-auto min-h-0"
|
||||
style={isSubjectTab
|
||||
style={isSubjectTab || isSceneTab
|
||||
? { flex: "0 0 360px", width: 360, minWidth: 320 }
|
||||
: { flex: "0 0 320px", width: 320, minWidth: 280, maxWidth: 340 }}
|
||||
>
|
||||
@@ -817,12 +910,24 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<option value="2048">2048</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-2 rounded-md border border-white/10 bg-black/25 px-2 py-1.5 text-[10px] leading-relaxed text-white/50">
|
||||
先用多张关键帧生成统一主体资产,再按当前关键帧去除主体并补全空场景;之后可生成相似新场景或同构换风格场景。
|
||||
</div>
|
||||
<div className="mb-2 grid grid-cols-2 gap-1.5">
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">生成方式</span>
|
||||
<div className="mb-2 rounded-md border border-white/10 bg-black/25 px-2 py-1.5 text-[10px] leading-relaxed text-white/50">
|
||||
左侧选择场景参考图,右侧选择地点和参考关键词;下方 prompt 可自动拼好,也可以手动改。
|
||||
</div>
|
||||
<div className="mb-2 grid grid-cols-2 gap-1.5">
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">地点</span>
|
||||
<select
|
||||
value={sceneLocation}
|
||||
onChange={(e) => setSceneLocation(e.target.value)}
|
||||
className="w-full rounded border border-white/10 bg-black/35 px-1.5 py-1 text-[10px] text-white/75 outline-none"
|
||||
>
|
||||
{SCENE_LOCATION_OPTIONS.map(([value, label]) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">生成方式</span>
|
||||
<select
|
||||
value={sceneMode}
|
||||
onChange={(e) => setSceneMode(e.target.value as SceneMode)}
|
||||
@@ -830,11 +935,13 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
>
|
||||
{SCENE_MODE_OPTIONS.map(([value, label]) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">风格</span>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mb-2 grid grid-cols-2 gap-1.5">
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">风格</span>
|
||||
<select
|
||||
value={sceneStyle}
|
||||
onChange={(e) => setSceneStyle(e.target.value as SceneStyle)}
|
||||
@@ -842,11 +949,63 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
>
|
||||
{SCENE_STYLE_OPTIONS.map(([value, label]) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{!hasSubjectAssets && (
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="block text-[9px] text-white/35">额外关键词</span>
|
||||
<input
|
||||
value={sceneExtraKeywords}
|
||||
onChange={(e) => setSceneExtraKeywords(e.target.value)}
|
||||
placeholder="例如:玻璃、金属、夜景"
|
||||
className="w-full rounded border border-white/10 bg-black/35 px-1.5 py-1 text-[10px] text-white/75 outline-none placeholder:text-white/25"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mb-2 rounded-md border border-white/10 bg-black/20 p-2">
|
||||
<div className="mb-1 text-[9px] text-white/35">参考要素</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{SCENE_REFERENCE_OPTIONS.map(([value, label]) => {
|
||||
const active = sceneReferenceKeys.includes(value)
|
||||
return (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setSceneReferenceKeys((prev) => (
|
||||
prev.includes(value)
|
||||
? prev.filter((item) => item !== value)
|
||||
: [...prev, value]
|
||||
))}
|
||||
className={`rounded border px-1.5 py-0.5 text-[9.5px] transition ${
|
||||
active
|
||||
? "border-emerald-300/60 bg-emerald-500/35 text-white"
|
||||
: "border-white/10 bg-black/25 text-white/45 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<label className="mb-2 block">
|
||||
<div className="mb-1 flex items-center justify-between gap-2">
|
||||
<span className="text-[9px] text-white/35">场景 prompt</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setScenePrompt(scenePromptDraft)}
|
||||
className="rounded bg-white/10 px-1.5 py-0.5 text-[9.5px] text-white/60 hover:bg-white/18 hover:text-white"
|
||||
>
|
||||
重组
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={scenePrompt || scenePromptDraft}
|
||||
onChange={(e) => setScenePrompt(e.target.value)}
|
||||
className="h-28 w-full resize-none rounded-md border border-white/10 bg-black/35 px-2 py-1.5 text-[10px] leading-relaxed text-white/75 outline-none"
|
||||
/>
|
||||
</label>
|
||||
{!hasSubjectAssets && (
|
||||
<div className="mb-2 rounded border border-amber-300/25 bg-amber-500/10 px-2 py-1.5 text-[10px] leading-snug text-amber-100/85">
|
||||
还没有主体资产。先在“主体资产”页生成主体图,场景图才能更准确地去主体和补背景。
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user