auto-save 2026-05-14 07:45 (~6)
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 1,
|
||||
"hash": "f8cd466",
|
||||
"message": "auto-save 2026-05-12 21:50 (~1)",
|
||||
"ts": "2026-05-12T21:50:16+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 1,
|
||||
"hash": "e6ef193",
|
||||
"message": "auto-save 2026-05-12 21:55 (~1)",
|
||||
"ts": "2026-05-12T21:56:09+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 1,
|
||||
"hash": "1fc0ae9",
|
||||
@@ -3335,6 +3321,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 07:34 (~1)",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-14T07:40:07+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-14 07:39 (~4)",
|
||||
"hash": "c263af2",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T23:43:15Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 07:39 (~4)",
|
||||
"files_changed": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ api/main.py
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
<div><strong>你看到的区域</strong><span>关键帧素材审核面板</span></div>
|
||||
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧改为 6 行镜头表:产品图、白底人物图、人物图上的产品区域、场景图和描述词一一对应;右侧承载当前镜头秒数、GPT Image 2 / Seedance 固定模型、AI 描述草稿、单条生成和批量排队。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt,再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code>、<code>addElement</code>、<code>generateSubjectAssets</code>、<code>generateSceneAsset</code>、<code>listProductLibrary</code>、<code>copyProductLibraryAsset</code> 和 <code>createProductFusionGuide</code>。</span></div>
|
||||
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧改为 6 行镜头表:产品图、白底人物图、人物图上的产品区域、场景图和描述词一一对应;产品融合槽位的“粘贴”优先使用应用内 <code>clipboard</code>,也支持选中槽位后 Cmd+V 粘贴系统图片。右侧承载当前镜头秒数、GPT Image 2 / Seedance 固定模型、AI 描述草稿、单条生成和批量排队。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt,再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code>、<code>addElement</code>、<code>generateSubjectAssets</code>、<code>generateSceneAsset</code>、<code>listProductLibrary</code>、<code>copyProductLibraryAsset</code>、<code>createProductFusionGuide</code> 和 <code>generateProductFusionDescriptions</code>。</span></div>
|
||||
<div><strong>适合怎么描述</strong><span>“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图、产品融合镜头组和质量风险应该如何审核”。</span></div>
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
@@ -765,6 +765,7 @@ SubjectAsset {
|
||||
<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>POST /jobs/{id}/product-fusion/guide</code></td><td><code>createProductFusionGuide</code></td><td>读取产品图和白底人物图,按用户在人物图上画出的 <code>product_region</code> 合成一张位置引导图;前端固定显示图片模型为 GPT Image 2,返回普通 <code>asset</code> 作为 Seedance 首帧。</td></tr>
|
||||
<tr><td>产品融合描述词</td><td><code>POST /jobs/{id}/product-fusion/descriptions</code></td><td><code>generateProductFusionDescriptions</code></td><td>为 6 行产品融合镜头生成动作描述草稿;有 LLM 配置时用 <code>REWRITE_MODEL</code> 生成 JSON,无配置或失败时回退到本地镜头模板。</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>
|
||||
@@ -883,10 +884,22 @@ SubjectAsset {
|
||||
<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>product_region</code> 合成到白底人物图上,生成普通 <code>asset</code> 引导图;前端再把该引导图作为 Seedance 首帧,并把产品图、人物图、场景图作为参考图提交。</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>
|
||||
|
||||
@@ -650,11 +650,12 @@ export default function Home() {
|
||||
if (typeof idx === "number") setStoryboardFrame(idx)
|
||||
setWorkbenchOpen(true)
|
||||
},
|
||||
clipboard,
|
||||
onCopyImage: handleCopyImage,
|
||||
onGenerateProductFusionVideo: handleGenerateProductFusionVideo,
|
||||
pinnedNodes,
|
||||
onToggleNodePin: handleToggleNodePin,
|
||||
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, setJob, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin])
|
||||
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, setJob, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, clipboard, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin])
|
||||
|
||||
// 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag)
|
||||
const savedSizes = useMemo(() => loadNodeSizes(), [])
|
||||
|
||||
@@ -327,6 +327,7 @@ export const Dashboard = forwardRef<DashboardHandle, Props>(function Dashboard({
|
||||
data.onCloseExpandedFrame()
|
||||
setExpanded(new Set([key]))
|
||||
}}
|
||||
clipboard={data.clipboard}
|
||||
onCopyImage={data.onCopyImage}
|
||||
onGenerateProductFusionVideo={data.onGenerateProductFusionVideo}
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,7 @@ interface Props {
|
||||
onToggleSelect: (idx: number) => void
|
||||
onJobUpdate?: (job: Job) => void
|
||||
onSwitchPanel?: (key: string) => void
|
||||
clipboard?: ImageRef | null
|
||||
onCopyImage?: (ref: ImageRef) => void
|
||||
onGenerateProductFusionVideo?: (frameIdx: number, shot: ProductFusionShot) => Promise<void> | void
|
||||
embedded?: boolean
|
||||
@@ -131,7 +132,7 @@ const normalizeFusionShots = (shots?: ProductFusionShot[] | null): ProductFusion
|
||||
return base.map((item, i) => ({ ...item, ...(shots[i] ?? {}), id: shots[i]?.id || item.id }))
|
||||
}
|
||||
|
||||
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, onCopyImage, onGenerateProductFusionVideo, embedded = false }: Props) {
|
||||
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, clipboard, onCopyImage, onGenerateProductFusionVideo, embedded = false }: Props) {
|
||||
const [describing, setDescribing] = useState(false)
|
||||
const [cleaningFrameIds, setCleaningFrameIds] = useState<Set<number>>(new Set())
|
||||
const [batchCleaning, setBatchCleaning] = useState(false)
|
||||
@@ -982,10 +983,18 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (clipboard) {
|
||||
assignFusionImage(slot, clipboard)
|
||||
toast.success(`已粘贴到「${label}」:${clipboard.label || "剪贴板图片"}`)
|
||||
return
|
||||
}
|
||||
setFusionUploadTarget(slot)
|
||||
toast.message(`已选中「${label}」槽位,现在可 Cmd+V 粘贴图片`)
|
||||
toast.message(`应用剪贴板为空;已选中「${label}」槽位,现在可 Cmd+V 粘贴系统图片`)
|
||||
}}
|
||||
className="rounded bg-white/10 px-1.5 py-0.5 text-[9px] text-white/60 hover:bg-white/18 hover:text-white"
|
||||
className={`rounded px-1.5 py-0.5 text-[9px] hover:bg-white/18 hover:text-white ${
|
||||
clipboard ? "bg-violet-500/60 text-white" : "bg-white/10 text-white/60"
|
||||
}`}
|
||||
title={clipboard ? `粘贴应用剪贴板:${clipboard.label || "图片"}` : "应用剪贴板为空;可先点槽位后 Cmd+V 粘贴系统图片"}
|
||||
>
|
||||
粘贴
|
||||
</button>
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface NodeData {
|
||||
onDeleteCutout?: (frameIdx: number, elementId: string, cutoutId: string) => void // 删元素提取图
|
||||
onOpenStoryboard?: (frameIdx: number) => void // 打开分镜头编排专属面板
|
||||
onOpenWorkbench?: (frameIdx?: number) => void // 展开顶部分镜编排内嵌面板
|
||||
clipboard?: ImageRef | null
|
||||
onCopyImage?: (ref: ImageRef) => void // 复制图片到全局剪贴板(粘贴到分镜头编排插槽)
|
||||
onGenerateProductFusionVideo?: (frameIdx: number, shot: ProductFusionShot) => Promise<void> | void
|
||||
pinnedNodes?: Set<string> // 已钉住的节点 id 集合 — 钉住后位置 + 尺寸锁定
|
||||
@@ -1957,6 +1958,7 @@ export function KeyframePanelNode({ data }: any) {
|
||||
onChange={d.onExpandFrame}
|
||||
onToggleSelect={d.onToggleFrame}
|
||||
onJobUpdate={d.onJobUpdate}
|
||||
clipboard={d.clipboard}
|
||||
onCopyImage={d.onCopyImage}
|
||||
onGenerateProductFusionVideo={d.onGenerateProductFusionVideo}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user