diff --git a/.memory/worklog.json b/.memory/worklog.json index ced2950..07fb61f 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -3309,6 +3309,19 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 3 项未提交变更 · 最近提交:auto-save 2026-05-14 05:05 (~6)", "files_changed": 3 + }, + { + "ts": "2026-05-14T05:10:53+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 05:10 (~3)", + "hash": "c7ca830", + "files_changed": 3 + }, + { + "ts": "2026-05-13T21:13:13Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 05:10 (~3)", + "files_changed": 2 } ] } diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index 7d00f36..10d5ce7 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -47,6 +47,15 @@ const LIVING_VIEW_OPTIONS = [ ["action_use", "使用"], ] +type LightboxTab = "clean" | "scene" | "subject" | "review" + +const LIGHTBOX_TABS: Array<{ key: LightboxTab; label: string }> = [ + { key: "clean", label: "原图/清洗" }, + { key: "scene", label: "场景图" }, + { key: "subject", label: "主体包" }, + { key: "review", label: "审核" }, +] + export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, onCopyImage, embedded = false }: Props) { const [describing, setDescribing] = useState(false) const [cleaning, setCleaning] = useState(false) @@ -60,6 +69,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o const [subjectKinds, setSubjectKinds] = useState>({}) const [subjectBackgrounds, setSubjectBackgrounds] = useState>({}) const [subjectViews, setSubjectViews] = useState>({}) + const [activeTab, setActiveTab] = useState("clean") const [editingElement, setEditingElement] = useState<{ id: string name_zh: string @@ -119,6 +129,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o const latestSceneAsset = f.scene_assets?.[f.scene_assets.length - 1] ?? null 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 cutoutCount = elements.reduce((sum, item) => sum + ((item.cutouts?.length ?? 0) || (item.cutout_id ? 1 : 0)), 0) + const qualityWarnings = [ + ...(f.quality_report?.warnings ?? []), + ...(latestSceneAsset?.quality_report?.warnings ?? []), + ] const handleDescribe = async () => { setDescribing(true) @@ -424,6 +440,28 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o )} +
+ {LIGHTBOX_TABS.map((tab) => ( + + ))} +
+ {latestSceneAsset ? "场景已生成" : "场景待生成"} + · + {subjectAssetCount > 0 ? `${subjectAssetCount} 主体资产` : "主体待生成"} +
+
+ {/* 主体 — 左:大图 + 清洗 / 选用;右:识别 + 元素清单 */}
{/* 左侧大图区 */} @@ -485,6 +523,8 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o )}
+ {activeTab === "clean" && ( + <> {/* 画框工具栏 */} {cropMode ? ( extractNamePrompt ? ( @@ -623,7 +663,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o {cleaning ? : } {cleaning ? "清洗中…(5-15 秒)" : hasCleaned ? "重新清洗" : f.cleaned_applied ? "再次清洗" : "🧹 清洗水印"} + + )} + {activeTab === "scene" && (
场景图
@@ -673,6 +716,37 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o {sceneGenerating ? "生成场景图中…" : latestSceneAsset ? "重新生成场景图" : "生成场景图"}
+ )} + + {activeTab === "review" && ( +
+
素材审核
+
+
+ {f.cleaned_applied ? "已应用清洗" : hasCleaned ? "清洗待确认" : "未清洗"} +
+
+ {latestSceneAsset ? "场景图已生成" : "场景图未生成"} +
+
0 ? "border-violet-300/35 bg-violet-500/12 text-violet-100" : "border-white/10 bg-black/25 text-white/55"}`}> + {subjectAssetCount > 0 ? `${subjectAssetCount} 张主体资产` : "主体包未生成"} +
+
+ {qualityWarnings.length ? `${qualityWarnings.length} 个风险` : "质量可用"} +
+
+ {qualityWarnings.length > 0 && ( +
+ {qualityWarnings.slice(0, 3).map((warning, i) => ( +
{warning}
+ ))} +
+ )} +
+ 审核通过后,把场景图和主体资产复制到分镜槽位;当前不会自动覆盖素材,避免模型误改细节。 +
+
+ )} @@ -1327,6 +1418,26 @@ export function VisualLabNode({ data, selected }: any) { )} + {(p.kind === "scene" || p.kind === "subject") && d.onCopyImage && ( + + )} + {p.kind === "cutout" && d.onCopyImage && ( + + -
-
{videos.length}
-
视频任务
-
{frames.length > 0 ? ( <> - {cleanedCount} 已清洗 · {sceneAssetCount} 场景图 · {subjectAssetCount || cutoutCount} 主体素材 · {d.selectedFrames.size}/{frames.length} 入编排 · {completedVideos.length} 已完成 + {cleanedCount} 已清洗 · {sceneAssetCount} 场景图 · {subjectAssetCount || cutoutCount} 主体素材 · {selectedFrameCount}/{frames.length} 入编排 · {completedVideos.length} 已完成 ) : ( - "解析后这里展示关键帧、元素和视频任务;具体处理仍在点击后的工作台完成。" + "解析后这里变成素材准备看板:先审关键帧,再生成场景图和主体资产包。" )}