auto-save 2026-05-14 06:16 (~5)
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 3,
|
||||
"hash": "4138bea",
|
||||
"message": "auto-save 2026-05-12 17:00 (~3)",
|
||||
"ts": "2026-05-12T17:01:09+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 4,
|
||||
"hash": "94afd6d",
|
||||
"message": "auto-save 2026-05-12 17:06 (+1, ~3)",
|
||||
"ts": "2026-05-12T17:06:43+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 3,
|
||||
"hash": "e1bc89a",
|
||||
@@ -3353,6 +3339,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-14 06:05 (~1)",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-14T06:11:29+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-14 06:11 (~6)",
|
||||
"hash": "871ced6",
|
||||
"files_changed": 6
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T22:13:14Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 06:11 (~6)",
|
||||
"files_changed": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2077,15 +2077,18 @@ def generate_scene_asset(job_id: str, idx: int, req: GenerateSceneAssetReq) -> J
|
||||
|
||||
confirmed_subjects = [
|
||||
(e.name_en or e.name_zh).strip()
|
||||
for e in (frame.elements or [])
|
||||
for ref_frame in job.frames
|
||||
for e in (ref_frame.elements or [])
|
||||
if (e.subject_assets or [])
|
||||
]
|
||||
if not confirmed_subjects:
|
||||
confirmed_subjects = [
|
||||
(e.name_en or e.name_zh).strip()
|
||||
for e in (frame.elements or [])
|
||||
for ref_frame in job.frames
|
||||
for e in (ref_frame.elements or [])
|
||||
if (e.name_en or e.name_zh).strip()
|
||||
][:3]
|
||||
confirmed_subjects = list(dict.fromkeys([x for x in confirmed_subjects if x]))[:3]
|
||||
subject_clause = (
|
||||
"Confirmed foreground subject(s) to remove: " + ", ".join(confirmed_subjects) + ". "
|
||||
if confirmed_subjects
|
||||
|
||||
@@ -682,6 +682,8 @@ api/main.py
|
||||
<pre>SceneAsset {
|
||||
id, label, url,
|
||||
width, height, quality, size,
|
||||
scene_mode: remove_subject | similar | style,
|
||||
scene_style,
|
||||
quality_report
|
||||
}
|
||||
|
||||
@@ -859,7 +861,7 @@ SubjectAsset {
|
||||
<span class="tag blue">Layout</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>“原图/清洗、场景图、主体资产、审核”都应遵循同一结构:左侧负责看图和框选,右侧负责操作、状态和结果;旧布局把部分操作塞在左侧下方,导致左侧满、右侧空。</p>
|
||||
<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>
|
||||
@@ -896,7 +898,7 @@ SubjectAsset {
|
||||
</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>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>
|
||||
@@ -907,20 +909,20 @@ SubjectAsset {
|
||||
<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>画面工作台从展示缩略图扩展为素材生产中枢后,关键帧、主体资产包、场景图和视频任务继续混在一个列表里会让流程不清晰;关键帧详情面板也把清洗、识别、主体生成和场景生成都堆在一屏。</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>
|
||||
<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>generateSceneAsset</code> 和 <code>generateSubjectAssets</code>,主体资产支持白/黑背景、原尺寸/固定尺寸、物体视角、人物/生物动作与喜怒哀乐等表情;当已选关键帧共同指向一个主体时,前端会把这些帧作为 <code>source_frame_indices</code> 传入,后端拼接参考板。</p>
|
||||
<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/<jobId>/assets</code>,可作为 <code>asset</code> 类型复制到后续分镜槽位。</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -86,6 +86,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const [subjectViews, setSubjectViews] = useState<Record<string, string[]>>({})
|
||||
const [activeTab, setActiveTab] = useState<LightboxTab>("clean")
|
||||
const [editingElement, setEditingElement] = useState<{
|
||||
frameIndex: number
|
||||
id: string
|
||||
name_zh: string
|
||||
name_en: string
|
||||
@@ -138,8 +139,28 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const cleanedFrameCount = frames.filter((frame) => frame.cleaned_applied || frame.cleaned_url).length
|
||||
const pendingCleanFrames = frames.filter((frame) => !frame.cleaned_applied && !frame.cleaned_url)
|
||||
const selectedFrameIndices = Array.from(selected).sort((a, b) => a - b)
|
||||
const sharedSubjectFrameIndices = selectedFrameIndices.length > 1 ? selectedFrameIndices : [f.index]
|
||||
const subjectAssetCount = elements.reduce((sum, item) => sum + (item.subject_assets?.length ?? 0), 0)
|
||||
const subjectReferenceFrameIndices = (selectedFrameIndices.length > 0 ? selectedFrameIndices : frames.map((frame) => frame.index))
|
||||
.filter((idx, pos, arr) => arr.indexOf(idx) === pos)
|
||||
const subjectReferenceLabel = selectedFrameIndices.length > 0
|
||||
? `${subjectReferenceFrameIndices.length} 已选帧参考`
|
||||
: `${subjectReferenceFrameIndices.length} 全部帧参考`
|
||||
const subjectElementRefs = frames.flatMap((frame) =>
|
||||
(frame.elements ?? []).map((element) => ({
|
||||
frameIndex: frame.index,
|
||||
frameLabel: `分镜 ${frame.index + 1}`,
|
||||
element,
|
||||
})),
|
||||
)
|
||||
const activeSubjectRefs = elements.map((element) => ({
|
||||
frameIndex: f.index,
|
||||
frameLabel: `分镜 ${f.index + 1}`,
|
||||
element,
|
||||
}))
|
||||
const subjectDisplayRefs = activeSubjectRefs.length > 0
|
||||
? activeSubjectRefs
|
||||
: subjectElementRefs.slice(0, 1)
|
||||
const hasUnifiedSubject = subjectElementRefs.length > 0
|
||||
const subjectAssetCount = subjectElementRefs.reduce((sum, item) => sum + (item.element.subject_assets?.length ?? 0), 0)
|
||||
const hasSubjectAssets = subjectAssetCount > 0
|
||||
const qualityWarnings = [
|
||||
...(f.quality_report?.warnings ?? []),
|
||||
@@ -231,21 +252,21 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateSubjectPackage = async (elementId: string) => {
|
||||
const handleGenerateSubjectPackage = async (elementId: string, frameIdx = f.index) => {
|
||||
const kind = subjectKinds[elementId] ?? "object"
|
||||
const defaultViews = (kind === "living" ? LIVING_VIEW_OPTIONS : OBJECT_VIEW_OPTIONS).map(([value]) => value)
|
||||
const views = subjectViews[elementId]?.length ? subjectViews[elementId] : defaultViews
|
||||
setSubjectGenerating(elementId)
|
||||
try {
|
||||
const updated = await generateSubjectAssets(jobId, f.index, elementId, {
|
||||
const updated = await generateSubjectAssets(jobId, frameIdx, elementId, {
|
||||
subject_kind: kind,
|
||||
background: subjectBackgrounds[elementId] ?? "white",
|
||||
size: assetSize,
|
||||
source_frame_indices: sharedSubjectFrameIndices,
|
||||
source_frame_indices: subjectReferenceFrameIndices,
|
||||
views,
|
||||
})
|
||||
onJobUpdate?.(updated)
|
||||
toast.success(`主体资产包已生成 · ${views.length} 张`)
|
||||
toast.success(`统一主体资产包已生成 · ${views.length} 张 · ${subjectReferenceFrameIndices.length} 帧参考`)
|
||||
} catch (e) {
|
||||
toast.error("主体资产包生成失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
@@ -325,6 +346,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const handleAddElement = async (name_zh: string, name_en?: string, position?: string, source: "auto" | "manual" = "manual") => {
|
||||
const zh = name_zh.trim()
|
||||
if (!zh) return
|
||||
if (hasUnifiedSubject) {
|
||||
toast.message("当前流程只保留一个主体;如需更换,请先删除现有统一主体")
|
||||
return
|
||||
}
|
||||
try {
|
||||
const updated = await addElement(jobId, f.index, { name_zh: zh, name_en, position, source })
|
||||
onJobUpdate?.(updated)
|
||||
@@ -333,9 +358,9 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteElement = async (id: string) => {
|
||||
const handleDeleteElement = async (id: string, frameIdx = f.index) => {
|
||||
try {
|
||||
const updated = await deleteElement(jobId, f.index, id)
|
||||
const updated = await deleteElement(jobId, frameIdx, id)
|
||||
onJobUpdate?.(updated)
|
||||
if (editingElement?.id === id) setEditingElement(null)
|
||||
} catch (e) {
|
||||
@@ -346,7 +371,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const handleUpdateElement = async () => {
|
||||
if (!editingElement || !editingElement.name_zh.trim()) return
|
||||
try {
|
||||
const updated = await updateElement(jobId, f.index, editingElement.id, {
|
||||
const updated = await updateElement(jobId, editingElement.frameIndex, editingElement.id, {
|
||||
name_zh: editingElement.name_zh,
|
||||
name_en: editingElement.name_en,
|
||||
position: editingElement.position,
|
||||
@@ -447,7 +472,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</button>
|
||||
))}
|
||||
<div className="ml-auto hidden items-center gap-2 text-[10px] text-white/42 sm:flex">
|
||||
<span>{hasSubjectAssets ? `${subjectAssetCount} 主体资产` : "主体待生成"}</span>
|
||||
<span>{hasSubjectAssets ? `统一主体 ${subjectAssetCount} 张` : hasUnifiedSubject ? "统一主体待生成" : "统一主体待选择"}</span>
|
||||
<span>·</span>
|
||||
<span>{latestSceneAsset ? "场景已生成" : "场景待生成"}</span>
|
||||
</div>
|
||||
@@ -677,7 +702,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</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">
|
||||
@@ -773,10 +798,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</div>
|
||||
<div className={`rounded border px-2 py-1 ${elements.length > 0 ? "border-violet-300/35 bg-violet-500/12 text-violet-100" : "border-white/10 bg-black/25 text-white/55"}`}>
|
||||
<div className="text-[9px] opacity-70">主体</div>
|
||||
<div>{elements.length} 个</div>
|
||||
<div>{hasUnifiedSubject ? "1 个" : "未选"}</div>
|
||||
</div>
|
||||
<div className={`rounded border px-2 py-1 ${subjectAssetCount > 0 ? "border-violet-300/35 bg-violet-500/12 text-violet-100" : "border-white/10 bg-black/25 text-white/55"}`}>
|
||||
<div className="text-[9px] opacity-70">资产</div>
|
||||
<div className="text-[9px] opacity-70">主体包</div>
|
||||
<div>{subjectAssetCount} 张</div>
|
||||
</div>
|
||||
<div className={`rounded border px-2 py-1 ${latestSceneAsset ? "border-emerald-300/30 bg-emerald-500/10 text-emerald-100" : "border-white/10 bg-black/25 text-white/55"}`}>
|
||||
@@ -805,7 +830,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1.5 text-white text-[12.5px] font-semibold">
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
主体识别
|
||||
统一主体识别
|
||||
{desc && <span className="text-[10px] text-emerald-400 font-mono ml-1">已识别</span>}
|
||||
</div>
|
||||
<button
|
||||
@@ -827,7 +852,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
画面主体识别中…
|
||||
</div>
|
||||
) : (
|
||||
<>点击「识别主体」让 Vision 给出可制作主体资产的候选对象</>
|
||||
<>点击「识别主体」从当前帧找出统一主体候选;多张关键帧会作为该主体的参考帧</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -847,20 +872,23 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
{desc.objects && desc.objects.length > 0 && (
|
||||
<div className="rounded-md bg-pink-500/10 border border-pink-400/25 px-2.5 py-1.5">
|
||||
<div className="text-[9.5px] uppercase tracking-widest text-pink-300/90 mb-1.5">
|
||||
候选主体 · 点击加入「主体清单」
|
||||
候选主体 · 选择唯一主体
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{desc.objects.map((o, i) => {
|
||||
const alreadyIn = elements.some((e) => e.name_zh === o.name)
|
||||
const locked = hasUnifiedSubject && !alreadyIn
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => !alreadyIn && handleAddElement(o.name, o.extract_prompt, o.position, "auto")}
|
||||
disabled={alreadyIn}
|
||||
title={alreadyIn ? "已加入主体清单" : (o.position ? `位置:${o.position}` : undefined)}
|
||||
onClick={() => !alreadyIn && !locked && handleAddElement(o.name, o.extract_prompt, o.position, "auto")}
|
||||
disabled={alreadyIn || locked}
|
||||
title={alreadyIn ? "已选择为统一主体" : locked ? "当前流程只保留一个主体;删除现有主体后可更换" : (o.position ? `位置:${o.position}` : undefined)}
|
||||
className={`w-full text-left rounded border px-2 py-1 transition group/o ${
|
||||
alreadyIn
|
||||
? "bg-emerald-500/10 border-emerald-400/30 cursor-default"
|
||||
: locked
|
||||
? "bg-white/[0.02] border-white/8 opacity-45 cursor-not-allowed"
|
||||
: "bg-white/[0.03] hover:bg-white/[0.08] border-white/8 hover:border-pink-300/40"
|
||||
}`}
|
||||
>
|
||||
@@ -889,21 +917,27 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<section className={activeTab === "subject" ? "" : "hidden"}>
|
||||
<div className="flex items-center gap-1.5 mb-2 text-white text-[12.5px] font-semibold">
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
主体清单
|
||||
{elements.length > 0 && (
|
||||
<span className="text-[10px] text-white/35 font-mono ml-0.5">· {elements.length}</span>
|
||||
统一主体
|
||||
{hasUnifiedSubject && (
|
||||
<span className="text-[10px] text-white/35 font-mono ml-0.5">· 已选择</span>
|
||||
)}
|
||||
<span className="text-[9.5px] text-white/35 font-normal ml-auto">→ 先修正,再生成资产</span>
|
||||
<span className="text-[9.5px] text-white/35 font-normal ml-auto">→ {subjectReferenceLabel}</span>
|
||||
</div>
|
||||
<div className="mb-2 rounded-md border border-white/10 bg-white/[0.035] px-2.5 py-1.5 text-[10.5px] leading-relaxed text-white/45">
|
||||
Vision 只负责给主体候选。这里确认名称、类型、背景和需要的视角/动作/表情,再生成主体资产包。
|
||||
当前工作流只保留一个主体。多张关键帧用于补齐这个主体的角度、动作和表情;场景图会按每张关键帧单独生成。
|
||||
</div>
|
||||
|
||||
{elements.length > 0 && (
|
||||
{!hasUnifiedSubject && (
|
||||
<div className="mb-2 rounded-lg border border-dashed border-white/15 bg-white/[0.03] p-3 text-[11px] leading-relaxed text-white/48">
|
||||
先在上方识别并选择一个主体;未手动勾选关键帧时,默认使用全部关键帧作为主体参考。
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subjectDisplayRefs.length > 0 && (
|
||||
<div className="space-y-2 mb-2">
|
||||
{elements.map((e) => {
|
||||
{subjectDisplayRefs.map(({ frameIndex, frameLabel, element: e }) => {
|
||||
const hasRegion = !!e.region
|
||||
const isEditing = editingElement?.id === e.id
|
||||
const isEditing = editingElement?.id === e.id && editingElement.frameIndex === frameIndex
|
||||
const currentKind = subjectKinds[e.id] ?? e.subject_kind ?? "object"
|
||||
const currentBg = subjectBackgrounds[e.id] ?? e.cutout_background ?? "white"
|
||||
const viewOptions = currentKind === "living" ? LIVING_VIEW_OPTIONS : OBJECT_VIEW_OPTIONS
|
||||
@@ -946,6 +980,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1 text-white text-[12px] font-medium leading-tight">
|
||||
<span className="truncate">{e.name_zh}</span>
|
||||
<span className="text-[8.5px] text-white/35 font-mono shrink-0">· {frameLabel}</span>
|
||||
{e.source === "auto" && (
|
||||
<span className="text-[8.5px] text-pink-300/70 font-mono">识别</span>
|
||||
)}
|
||||
@@ -992,6 +1027,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<>
|
||||
<button
|
||||
onClick={() => setEditingElement({
|
||||
frameIndex,
|
||||
id: e.id,
|
||||
name_zh: e.name_zh,
|
||||
name_en: e.name_en || "",
|
||||
@@ -1003,9 +1039,9 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
改名
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteElement(e.id)}
|
||||
onClick={() => handleDeleteElement(e.id, frameIndex)}
|
||||
className="ml-auto rounded px-2 py-1 text-[10.5px] inline-flex items-center gap-1 bg-rose-500/15 hover:bg-rose-500/30 text-rose-100 border border-rose-300/20"
|
||||
title="删除这个主体候选和它的主体资产"
|
||||
title="删除这个统一主体候选和它的主体资产"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
删主体
|
||||
@@ -1015,12 +1051,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 rounded-md border border-violet-300/15 bg-violet-500/[0.08] p-2">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="text-[11px] font-semibold text-white/90">主体资产包</div>
|
||||
<span className="text-[9.5px] font-mono text-white/35">
|
||||
{sharedSubjectFrameIndices.length > 1 ? `${sharedSubjectFrameIndices.length} 帧参考` : "当前帧参考"}
|
||||
</span>
|
||||
<div className="mt-2 rounded-md border border-violet-300/15 bg-violet-500/[0.08] p-2">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="text-[11px] font-semibold text-white/90">统一主体资产包</div>
|
||||
<span className="text-[9.5px] font-mono text-white/35">
|
||||
{subjectReferenceLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-2 grid grid-cols-3 gap-1">
|
||||
<select
|
||||
@@ -1043,12 +1079,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<option value="white">白底</option>
|
||||
<option value="black">黑底</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleGenerateSubjectPackage(e.id)}
|
||||
disabled={isSubjectGenerating || activeViews.length === 0}
|
||||
className="rounded bg-violet-500/70 px-1.5 py-1 text-[10px] font-medium text-white transition hover:bg-violet-400 disabled:cursor-wait disabled:opacity-45 inline-flex items-center justify-center gap-1"
|
||||
title="生成多视角 / 动作 / 表情主体资产"
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleGenerateSubjectPackage(e.id, frameIndex)}
|
||||
disabled={isSubjectGenerating || activeViews.length === 0}
|
||||
className="rounded bg-violet-500/70 px-1.5 py-1 text-[10px] font-medium text-white transition hover:bg-violet-400 disabled:cursor-wait disabled:opacity-45 inline-flex items-center justify-center gap-1"
|
||||
title="用多张关键帧参考生成同一个主体的多视角 / 动作 / 表情资产"
|
||||
>
|
||||
{isSubjectGenerating ? <Loader2 className="h-3 w-3 animate-spin" /> : <Wand2 className="h-3 w-3" />}
|
||||
{isSubjectGenerating ? "生成" : "生成"}
|
||||
@@ -1114,7 +1150,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
)}
|
||||
|
||||
<div className="mt-1.5 text-[10px] text-white/35 leading-relaxed">
|
||||
主体候选来自识别结果;确认名称、类型和视角后生成主体资产包。
|
||||
主体候选来自识别结果;确认唯一主体、类型和视角后,使用参考帧生成统一主体资产包。
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1561,13 +1561,13 @@ export function VisualLabNode({ data, selected }: any) {
|
||||
onClick={(e) => { e.stopPropagation(); openFirstFrame() }}
|
||||
disabled={!job || frames.length === 0}
|
||||
className="min-h-14 rounded-md border border-white/10 px-2 py-2 text-left transition hover:border-violet-300/50 hover:bg-violet-400/10 disabled:opacity-35"
|
||||
title="生成主体多视角 / 动作 / 表情资产包"
|
||||
title="用多张关键帧生成统一主体的多视角 / 动作 / 表情资产包"
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-1 text-[var(--text-strong)] text-[12px] font-semibold">
|
||||
<Package className="h-3 w-3 text-violet-300" />
|
||||
{subjectAssetCount}
|
||||
</div>
|
||||
<div>主体资产</div>
|
||||
<div>统一主体</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1599,7 +1599,7 @@ export function VisualLabNode({ data, selected }: any) {
|
||||
<div className="mt-2 text-[10.5px] leading-snug text-[var(--text-faint)]">
|
||||
{frames.length > 0 ? (
|
||||
<>
|
||||
{cleanedCount} 已清洗 · {subjectAssetCount} 主体资产 · {sceneAssetCount} 场景图 · {targetFrameCount} 素材帧 · {completedVideos.length} 已完成
|
||||
{cleanedCount} 已清洗 · 统一主体 {subjectAssetCount} 张 · {sceneAssetCount} 场景图 · {targetFrameCount} 素材帧 · {completedVideos.length} 已完成
|
||||
</>
|
||||
) : (
|
||||
"解析后这里变成素材准备看板:先审关键帧,再生成主体资产包和去主体场景图。"
|
||||
|
||||
Reference in New Issue
Block a user