auto-save 2026-05-14 05:38 (~3)
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 0,
|
||||
"hash": "",
|
||||
"message": "项目创建: SKG AI 素材管线 - TK 二创验证",
|
||||
"ts": "2026-05-12T00:00:00+08:00",
|
||||
"type": "milestone"
|
||||
},
|
||||
{
|
||||
"files_changed": 7,
|
||||
"hash": "56d435f",
|
||||
"message": "init: project scaffold",
|
||||
"ts": "2026-05-12T15:37:36+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 4,
|
||||
"hash": "bbd41fa",
|
||||
@@ -3361,6 +3347,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 05:27 (~3)",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-14T05:32:54+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-14 05:32 (~4)",
|
||||
"hash": "f3636a5",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T21:33:13Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 05:32 (~4)",
|
||||
"files_changed": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ api/main.py
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>KeyElement</h3>
|
||||
<p>从关键帧里识别或手动添加的主体候选。Vision 给的是候选,用户可编辑、删除,并可基于它生成主体资产包。</p>
|
||||
<p>从关键帧识别结果里确认出来的主体候选。Vision 给的是候选,用户可编辑、删除,并可基于它生成主体资产包。</p>
|
||||
<pre>KeyElement {
|
||||
id,
|
||||
name_zh, name_en, position,
|
||||
@@ -788,7 +788,7 @@ SubjectAsset {
|
||||
<li>手动按时间戳加关键帧。</li>
|
||||
<li>关键帧清洗水印,全图或区域清洗。</li>
|
||||
<li>Vision 识别关键帧,输出 scene、objects、style、suggested_prompt,并作为主体候选来源。</li>
|
||||
<li>主体候选增改删、区域主体添加、主体资产包生成。</li>
|
||||
<li>主体候选确认、改名、删除和主体资产包生成。</li>
|
||||
<li>分镜工作台 4 图槽和改造说明自动保存。</li>
|
||||
<li>nano-banana-pro image-to-image 生图。</li>
|
||||
</ul>
|
||||
@@ -846,8 +846,8 @@ SubjectAsset {
|
||||
<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>面板标题仍叫“关键帧详情 · 元素提取”,里面还露出普通 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>
|
||||
@@ -859,7 +859,7 @@ SubjectAsset {
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>画面工作台从展示缩略图扩展为素材生产中枢后,关键帧、场景图、主体资产包和视频任务继续混在一个列表里会让流程不清晰;关键帧详情面板也把清洗、识别、场景和主体生成都堆在一屏。</p>
|
||||
<p><strong>改动:</strong><code>VisualLabNode</code> 改成素材准备进度看板,显示目标关键帧、场景图、主体资产和分镜/视频四个入口。<code>FrameLightbox</code> 新增“原图/清洗、场景图、主体资产、审核”四个页签,素材审核信息从普通列表中拆出来。</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>
|
||||
|
||||
@@ -62,8 +62,6 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const [applying, setApplying] = useState(false)
|
||||
const [sceneGenerating, setSceneGenerating] = useState(false)
|
||||
const [subjectGenerating, setSubjectGenerating] = useState<string | null>(null)
|
||||
const [addingZh, setAddingZh] = useState(false)
|
||||
const [addInput, setAddInput] = useState("")
|
||||
const [assetSize, setAssetSize] = useState<AssetSize>("source")
|
||||
const [subjectKinds, setSubjectKinds] = useState<Record<string, SubjectKind>>({})
|
||||
const [subjectBackgrounds, setSubjectBackgrounds] = useState<Record<string, AssetBackground>>({})
|
||||
@@ -82,9 +80,6 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const [regions, setRegions] = useState<Region[]>([])
|
||||
const [draftRegion, setDraftRegion] = useState<Region | null>(null) // 当前正在拖的
|
||||
const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null)
|
||||
const [subjectRegionPrompt, setSubjectRegionPrompt] = useState(false)
|
||||
const [subjectRegionName, setSubjectRegionName] = useState("")
|
||||
const [addingRegionSubject, setAddingRegionSubject] = useState(false)
|
||||
const imgWrapRef = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
@@ -94,8 +89,6 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
setRegions([])
|
||||
setDraftRegion(null)
|
||||
setDragStart(null)
|
||||
setSubjectRegionPrompt(false)
|
||||
setSubjectRegionName("")
|
||||
}, [activeIndex])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -201,26 +194,6 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddRegionSubject = async () => {
|
||||
if (regions.length !== 1 || !subjectRegionName.trim()) return
|
||||
const r = regions[0]
|
||||
setAddingRegionSubject(true)
|
||||
try {
|
||||
const added = await addElement(jobId, f.index, {
|
||||
name_zh: subjectRegionName.trim(),
|
||||
source: "region",
|
||||
region: r,
|
||||
})
|
||||
onJobUpdate?.(added)
|
||||
toast.success(`「${subjectRegionName.trim()}」已加入主体清单`)
|
||||
setCropMode(false); setRegions([]); setSubjectRegionPrompt(false); setSubjectRegionName("")
|
||||
} catch (e) {
|
||||
toast.error("添加主体失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
setAddingRegionSubject(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 画框 mouse handlers — 坐标基于 img wrapper 相对位置
|
||||
const getRelXY = (clientX: number, clientY: number) => {
|
||||
const el = imgWrapRef.current
|
||||
@@ -284,14 +257,11 @@ 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
|
||||
setAddingZh(true)
|
||||
try {
|
||||
const updated = await addElement(jobId, f.index, { name_zh: zh, name_en, position, source })
|
||||
onJobUpdate?.(updated)
|
||||
} catch (e) {
|
||||
toast.error("加入失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
setAddingZh(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,92 +442,47 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<>
|
||||
{/* 画框工具栏 */}
|
||||
{cropMode ? (
|
||||
subjectRegionPrompt ? (
|
||||
<div className="space-y-1.5">
|
||||
<input
|
||||
autoFocus
|
||||
value={subjectRegionName}
|
||||
onChange={(e) => setSubjectRegionName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.nativeEvent.isComposing && subjectRegionName.trim()) {
|
||||
e.preventDefault()
|
||||
handleAddRegionSubject()
|
||||
}
|
||||
if (e.key === "Escape") { setSubjectRegionPrompt(false); setSubjectRegionName("") }
|
||||
}}
|
||||
placeholder="给这个主体起名(如:手持产品的人)"
|
||||
className="w-full text-[11.5px] px-2 py-1.5 rounded-md bg-black/40 border border-violet-300/50 outline-none text-white placeholder:text-white/30 focus:ring-2 focus:ring-violet-400/40"
|
||||
/>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
onClick={handleAddRegionSubject}
|
||||
disabled={addingRegionSubject || !subjectRegionName.trim()}
|
||||
className="flex-1 px-2 py-1.5 rounded-md text-[11px] font-medium inline-flex items-center justify-center gap-1 transition bg-violet-500 hover:bg-violet-400 text-white disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{addingRegionSubject ? <Loader2 className="h-3 w-3 animate-spin" /> : <Wand2 className="h-3 w-3" />}
|
||||
{addingRegionSubject ? "添加中…" : "添加主体"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setSubjectRegionPrompt(false); setSubjectRegionName("") }}
|
||||
className="px-2 py-1.5 rounded-md text-[11px] bg-white/10 hover:bg-white/20 text-white"
|
||||
title="返回"
|
||||
>
|
||||
<ChevronLeft className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] text-white/55 leading-snug">
|
||||
{regions.length === 0
|
||||
? "在图上拖动鼠标 → 框选要清洗的水印、字幕、平台 UI 或杂物"
|
||||
: `已框 ${regions.length} 个 · 继续加框或点击「去掉」批量清洗`}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] text-white/55 leading-snug">
|
||||
{regions.length === 0
|
||||
? "在图上拖动鼠标 → 框选要处理的区域(可连续画多个)"
|
||||
: `已框 ${regions.length} 个 · 继续加框 · 「去掉」批量清洗 · 单框可加入主体清单`}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => handleCleanup(true)}
|
||||
disabled={cleaning || regions.length === 0}
|
||||
className="flex-1 px-1.5 py-1.5 rounded-md text-[10.5px] font-medium inline-flex items-center justify-center gap-1 transition bg-cyan-500 hover:bg-cyan-400 text-white disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
title="批量清洗所有框内"
|
||||
>
|
||||
{cleaning ? <Loader2 className="h-3 w-3 animate-spin" /> : <Sparkle className="h-3 w-3" />}
|
||||
{cleaning ? "去掉中" : `✓ 去掉${regions.length > 1 ? ` ${regions.length}` : ""}`}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { if (regions.length === 1) setSubjectRegionPrompt(true) }}
|
||||
disabled={regions.length !== 1}
|
||||
className="flex-1 px-1.5 py-1.5 rounded-md text-[10.5px] font-medium inline-flex items-center justify-center gap-1 transition bg-violet-500 hover:bg-violet-400 text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title={regions.length === 1 ? "把框内对象加入主体清单" : regions.length === 0 ? "先画一个框" : "添加主体仅在恰好 1 个框时可用"}
|
||||
>
|
||||
<Wand2 className="h-3 w-3" />
|
||||
加主体
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRegions((prev) => prev.slice(0, -1))}
|
||||
disabled={regions.length === 0}
|
||||
className="px-1.5 py-1.5 rounded-md text-[10.5px] bg-white/10 hover:bg-white/20 text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title="撤销上一个框"
|
||||
>
|
||||
↶
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setCropMode(false); setRegions([]); setDraftRegion(null); setDragStart(null) }}
|
||||
className="px-1.5 py-1.5 rounded-md text-[10.5px] bg-white/10 hover:bg-white/20 text-white"
|
||||
title="退出画框"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => handleCleanup(true)}
|
||||
disabled={cleaning || regions.length === 0}
|
||||
className="flex-1 px-1.5 py-1.5 rounded-md text-[10.5px] font-medium inline-flex items-center justify-center gap-1 transition bg-cyan-500 hover:bg-cyan-400 text-white disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
title="批量清洗所有框内"
|
||||
>
|
||||
{cleaning ? <Loader2 className="h-3 w-3 animate-spin" /> : <Sparkle className="h-3 w-3" />}
|
||||
{cleaning ? "去掉中" : `去掉${regions.length > 1 ? ` ${regions.length}` : ""}`}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRegions((prev) => prev.slice(0, -1))}
|
||||
disabled={regions.length === 0}
|
||||
className="px-1.5 py-1.5 rounded-md text-[10.5px] bg-white/10 hover:bg-white/20 text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title="撤销上一个框"
|
||||
>
|
||||
↶
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setCropMode(false); setRegions([]); setDraftRegion(null); setDragStart(null) }}
|
||||
className="px-1.5 py-1.5 rounded-md text-[10.5px] bg-white/10 hover:bg-white/20 text-white"
|
||||
title="退出画框"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => { setCropMode(true); setRegions([]) }}
|
||||
className="w-full px-3 py-1.5 rounded-md text-[10.5px] font-medium inline-flex items-center justify-center gap-1.5 transition bg-white/[0.06] hover:bg-cyan-500/30 border border-white/15 hover:border-cyan-300/50 text-white/80 hover:text-white"
|
||||
title="可连续画多个框 · 批量清洗或单框添加主体"
|
||||
title="可连续画多个框 · 批量清洗局部水印或杂物"
|
||||
>
|
||||
<Crop className="h-3 w-3" />
|
||||
框选区域(可多框)
|
||||
框选清洗区域(可多框)
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -1050,34 +975,8 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 添加 */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input
|
||||
value={addInput}
|
||||
onChange={(ev) => setAddInput(ev.target.value)}
|
||||
onKeyDown={(ev) => {
|
||||
if (ev.key === "Enter" && !ev.nativeEvent.isComposing) {
|
||||
ev.preventDefault()
|
||||
if (addInput.trim() && !addingZh) {
|
||||
handleAddElement(addInput)
|
||||
setAddInput("")
|
||||
}
|
||||
}
|
||||
}}
|
||||
placeholder="添加主体候选 · 中文回车自动翻英文"
|
||||
className="flex-1 text-[12px] px-2.5 py-1.5 rounded-md bg-black/40 border border-white/15 outline-none text-white placeholder:text-white/30 focus:ring-2 focus:ring-violet-400/40 focus:border-violet-400/40"
|
||||
/>
|
||||
<button
|
||||
onClick={() => { if (addInput.trim() && !addingZh) { handleAddElement(addInput); setAddInput("") } }}
|
||||
disabled={!addInput.trim() || addingZh}
|
||||
className="text-[11.5px] px-2.5 py-1.5 rounded-md bg-violet-500/60 hover:bg-violet-500/80 text-white inline-flex items-center justify-center gap-1 disabled:opacity-30 disabled:cursor-not-allowed transition shrink-0"
|
||||
title="添加"
|
||||
>
|
||||
{addingZh ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-1.5 text-[10px] text-white/35 leading-relaxed">
|
||||
主体候选会持久化保存;主体资产包和场景图准备好后,再复制到「分镜头编排」。
|
||||
主体候选来自识别结果;确认名称、类型和视角后生成主体资产包。
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user