fix: clear subject conversion panel

This commit is contained in:
2026-05-20 14:16:58 +08:00
parent 52b839c614
commit f1c710edc2
4 changed files with 87 additions and 276 deletions

View File

@@ -1,63 +1,5 @@
{
"entries": [
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: add storyboard script rewriting",
"ts": "2026-05-17T12:58:29Z",
"type": "session-heartbeat"
},
{
"files_changed": 3,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 3 项未提交变更 · 最近提交feat: add storyboard script rewriting",
"ts": "2026-05-17T13:08:29Z",
"type": "session-heartbeat"
},
{
"files_changed": 4,
"hash": "252cdf4",
"message": "auto-save 2026-05-17 21:09 (~4)",
"ts": "2026-05-17T21:09:20+08:00",
"type": "commit"
},
{
"files_changed": 3,
"hash": "ab2d0a8",
"message": "auto-save 2026-05-17 21:14 (~3)",
"ts": "2026-05-17T21:14:42+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 21:14 (~3)",
"ts": "2026-05-17T13:18:29Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 21:14 (~3)",
"ts": "2026-05-17T13:28:29Z",
"type": "session-heartbeat"
},
{
"files_changed": 4,
"hash": "97a1f66",
"message": "auto-save 2026-05-17 21:36 (~4)",
"ts": "2026-05-17T21:36:46+08:00",
"type": "commit"
},
{
"files_changed": 3,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 3 项未提交变更 · 最近提交auto-save 2026-05-17 21:36 (~4)",
"ts": "2026-05-17T13:38:30Z",
"type": "session-heartbeat"
},
{
"files_changed": 3,
"hash": "bd86140",
"message": "auto-save 2026-05-17 21:42 (~3)",
"ts": "2026-05-17T21:42:09+08:00",
"type": "commit"
},
{
"files_changed": 2,
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 2 项未提交变更 · 最近提交auto-save 2026-05-17 21:42 (~3)",
@@ -3251,6 +3193,65 @@
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: make subject conversion dialog-driven",
"files_changed": 1
},
{
"ts": "2026-05-20T13:56:20+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 13:56 (+1, ~2)",
"hash": "97c68be",
"files_changed": 3
},
{
"ts": "2026-05-20T13:59:20+08:00",
"type": "commit",
"message": "docs: record dialog conversion deployment",
"hash": "52b839c",
"files_changed": 2
},
{
"ts": "2026-05-20T14:01:46+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 14:01 (+1, ~1)",
"hash": "43d35fb",
"files_changed": 2
},
{
"ts": "2026-05-20T06:03:39Z",
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-20 14:01 (+1, ~1)",
"files_changed": 1
},
{
"ts": "2026-05-20T06:03:58Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-20 14:01 (+1, ~1)",
"files_changed": 1
},
{
"ts": "2026-05-20T14:07:12+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 14:07 (~2)",
"hash": "2fe54d1",
"files_changed": 2
},
{
"ts": "2026-05-20T14:12:36+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 14:12 (~5)",
"hash": "6cd41a2",
"files_changed": 5
},
{
"ts": "2026-05-20T06:13:39Z",
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 分支 main · 3 项未提交变更 · 最近提交auto-save 2026-05-20 14:12 (~5)",
"files_changed": 3
},
{
"ts": "2026-05-20T06:13:58Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 3 项未提交变更 · 最近提交auto-save 2026-05-20 14:12 (~5)",
"files_changed": 3
}
]
}

View File

@@ -11,7 +11,7 @@
- 详见 `CLAUDE.md` 立项决策段 + `.memory/plan.md` 七步管线拆解
- 风格:`04-Dark-Gallery-Ambient`(路径:`~/Projects/research/20260305-网页风格库/04-Dark-Gallery-Ambient.md`
- 第一冲刺:步骤 1-4下载 / 拆轨 / 关键帧 / ASR+翻译)
- 当前产品方向2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区右侧主体链路是“参考帧池 → 转换层 → 主体元素”:参考帧池竖向排列;转换层改为项目内生图对话智能体,用户把 1-3 张参考帧拖入同一个参考区,选择 GPT 套件或 Gemini 套件,点击“开始分析”后由模型识别参考图关键特征、生成中文摘要、英文 generation brief、特征 chip、追问和风险提示。GPT 套件表示 GPT 分析/对话 + `gpt-image-2` 生图Gemini 套件表示 Gemini 分析/对话 + `gemini-3-pro-image-preview` 生图,默认不跨套件偷偷兜底。转换层对话只处理主体生图需求,允许在对话里修改形象锁定、卡通重构、创意复刻、自主描述、数量、服装统一、人物占比、保留/删除元素等;对话、模型套件和最终英文 prompt 写入 `Job.subject_agent` / `state.json`,不再把模型选择作为主状态存进浏览器本地记忆。点击生成后右侧主体元素区按每次生成的套图文件夹展示主体多视图,当前套图在最上层展开,其他套图顺位进入下方可滚动列表,同一方向允许保留多套。主体重构默认继承参考图里的性别、人种/肤色、年龄体态和角色气质这些广义特征,但生成同一个全新主体;形象锁定模式走同一可见主体参考重绘。后端会给每套视图注入同一份 pack bible锁定脸部设定、发型、体态、服装类型、配色、材质、剪裁和配饰并在保存时裁白边后允许放大主体到画布高度约 88-94%,避免一套图里每张衣服不同或人物太小。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
- 当前产品方向2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区右侧主体链路是“参考帧池 → 转换层 → 主体元素”:参考帧池竖向排列;转换层当前先清空为待重构占位,不再承接拖拽、模型选择、对话、分析或生成按钮;右侧主体元素区的套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑保持不变。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
## 部署事实
- 平台VPS `76.13.31.179`Ubuntu 24.04 / Docker Compose / Coolify Traefik

File diff suppressed because one or more lines are too long

View File

@@ -3769,7 +3769,7 @@ function SourceSubjectPipeline({
>
<div className="mb-1 flex items-center justify-between gap-2 text-[9.5px] text-white/30">
<span>{frames.length} </span>
<span>{filmstripDragging ? "松手加入" : "拖到转换层"}</span>
<span>{filmstripDragging ? "松手加入" : "点击选择"}</span>
</div>
<div className="flex max-h-[410px] flex-col gap-1 overflow-y-auto pr-0.5 2xl:max-h-[500px]">
{frames.map((frame, index) => {
@@ -3777,11 +3777,6 @@ function SourceSubjectPipeline({
return (
<div
key={frame.index}
draggable
onDragStart={(event) => {
event.dataTransfer.setData(SOURCE_KEYFRAME_DRAG_TYPE, String(frame.index))
event.dataTransfer.effectAllowed = "copy"
}}
className="relative"
>
<MediaAssetTile
@@ -3795,11 +3790,11 @@ function SourceSubjectPipeline({
previewPlacement="left"
previewMaxWidth={320}
previewClassName="p-2"
selected={selected || allConversionFrameIndices.has(frame.index)}
title={`${selected ? "已选 · 点击取消" : "点击选择"} · 拖到转换层生成主体套图`}
selected={selected}
title={selected ? "已选 · 点击取消" : "点击选择"}
onClick={() => onToggleFrame(frame.index)}
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
topRight={<span className="rounded-full bg-black/72 p-0.5">{allConversionFrameIndices.has(frame.index) ? <Sparkles className="h-3 w-3 text-[#f5d98e]" /> : selected ? <Check className="h-3 w-3 text-emerald-200" /> : <Circle className="h-3 w-3 text-white/50" />}</span>}
topRight={<span className="rounded-full bg-black/72 p-0.5">{selected ? <Check className="h-3 w-3 text-emerald-200" /> : <Circle className="h-3 w-3 text-white/50" />}</span>}
onDelete={onDeleteFrame ? () => onDeleteFrame(frame.index) : undefined}
deleting={deletingFrame === frame.index}
deleteLabel={`删除参考帧 ${index + 1}`}
@@ -3819,206 +3814,9 @@ function SourceSubjectPipeline({
<div className="min-w-0">
<div className="mb-2 flex items-center justify-between gap-2">
<SectionTitle icon={<Wand2 className="h-4 w-4" />} title="转换层" />
<span className="rounded-full border border-cyan-200/20 bg-cyan-300/[0.08] px-2 py-0.5 text-[9.5px] text-cyan-50/70">
{subjectModelBundleConfig(subjectModelBundle).detail}
</span>
</div>
<div
className={`max-h-[520px] min-h-[410px] overflow-y-auto rounded-md border p-2 transition 2xl:max-h-[600px] 2xl:min-h-[500px] ${
agentDropActive ? "border-[#d6b36a]/80 bg-[#d6b36a]/10 ring-1 ring-[#d6b36a]/45" : "border-white/10 bg-black/32"
}`}
onDragEnter={(event) => {
if (!Array.from(event.dataTransfer.types).includes(SOURCE_KEYFRAME_DRAG_TYPE)) return
event.preventDefault()
setAgentDropActive(true)
}}
onDragOver={(event) => {
if (!Array.from(event.dataTransfer.types).includes(SOURCE_KEYFRAME_DRAG_TYPE)) return
event.preventDefault()
event.dataTransfer.dropEffect = "copy"
}}
onDragLeave={(event) => {
const next = event.relatedTarget as Node | null
if (next && event.currentTarget.contains(next)) return
setAgentDropActive(false)
}}
onDrop={(event) => {
event.preventDefault()
setAgentDropActive(false)
const frameIndex = Number(event.dataTransfer.getData(SOURCE_KEYFRAME_DRAG_TYPE))
const frame = frames.find((item) => item.index === frameIndex)
if (frame) addAgentReferenceFrame(frame)
}}
>
<div className="grid grid-cols-2 gap-1">
{SUBJECT_MODEL_BUNDLE_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
onClick={() => setSubjectModelBundle(option.value)}
className={`min-h-9 rounded-md border px-2 py-1 text-left transition ${
subjectModelBundle === option.value
? "border-[#d6b36a]/70 bg-[#d6b36a]/16 text-white"
: "border-white/10 bg-black/28 text-white/50 hover:border-white/22 hover:text-white"
}`}
title={option.detail}
>
<span className="block text-[10.5px] font-semibold">{option.label}</span>
<span className="mt-0.5 block truncate text-[8.5px] text-white/36">{option.detail}</span>
</button>
))}
</div>
<div className="mt-2 rounded-md border border-white/10 bg-black/24 p-2">
<div className="mb-1.5 flex items-center justify-between gap-2">
<span className="text-[10px] font-semibold text-white/62"></span>
<span className="rounded-full border border-white/10 bg-black/35 px-1.5 py-0.5 font-mono text-[9px] text-white/42">
{agentReferenceFrames.length}/{RECONSTRUCTION_FRAME_LIMIT}
</span>
</div>
<div className="flex min-h-[64px] items-center gap-1.5 overflow-x-auto pb-0.5">
{agentReferenceFrames.map((frame, index) => (
<div key={frame.index} className="relative shrink-0">
<MediaAssetTile
src={effectiveFrameUrl(job.id, frame)}
alt={`转换层参考 ${index + 1}`}
label={String(index + 1).padStart(2, "0")}
meta={`${frame.timestamp.toFixed(1)}s`}
className="aspect-[9/16] w-[42px] 2xl:w-[46px]"
objectFit="contain"
disablePreview
topLeft={<span className="rounded bg-black/72 px-0.5 font-mono text-[8px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
/>
<button
type="button"
onClick={() => removeAgentReferenceFrame(frame.index)}
className="absolute -right-1 -top-1 z-20 inline-flex h-4 w-4 items-center justify-center rounded-full border border-rose-100/35 bg-black/82 text-rose-100 transition hover:border-rose-100/70 hover:bg-rose-500/25"
aria-label="移出转换层参考"
title="移出转换层参考"
>
<Trash2 className="h-2.5 w-2.5" />
</button>
</div>
))}
{!agentReferenceFrames.length ? (
<div className="flex min-h-[54px] flex-1 items-center justify-center rounded border border-dashed border-white/12 px-2 text-center text-[10px] leading-snug text-white/34">
</div>
) : null}
</div>
<button
type="button"
onClick={() => void runSubjectAgentAnalyze()}
disabled={subjectAgentBusy === "analyze" || !agentReferenceFrames.length}
className="mt-2 inline-flex h-8 w-full items-center justify-center gap-1 rounded-md border border-cyan-200/25 bg-cyan-300/[0.08] px-3 text-[10.5px] font-semibold text-cyan-50/78 transition hover:border-cyan-200/45 hover:bg-cyan-300/[0.12] disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectAgentBusy === "analyze" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <MessageSquare className="h-3.5 w-3.5" />}
</button>
</div>
<div className="mt-2 rounded-md border border-[#d6b36a]/25 bg-[#d6b36a]/[0.065] px-2.5 py-2">
<div className="flex flex-wrap items-center gap-1.5">
<span className="rounded-full border border-[#d6b36a]/35 bg-black/28 px-2 py-0.5 text-[9.5px] font-semibold text-[#f4dd98]">
{reconstructionModeConfig(agentMode).label}
</span>
<span className="rounded-full border border-white/10 bg-black/28 px-2 py-0.5 text-[9.5px] font-semibold text-white/58">
{selectedSubjectViews.length}
</span>
<span className="rounded-full border border-white/10 bg-black/28 px-2 py-0.5 text-[9.5px] text-white/42">
</span>
</div>
<div className="mt-1.5 text-[9.5px] leading-snug text-white/42">
/ / /
</div>
</div>
{agentAnalysis ? (
<div className="mt-2 rounded-md border border-cyan-200/18 bg-cyan-300/[0.055] p-2 text-[10px] leading-snug text-cyan-50/72">
<div className="mb-1 font-semibold text-cyan-50">AI </div>
<div>{agentAnalysis.summary_zh}</div>
{agentAnalysis.questions.length ? (
<div className="mt-1.5 text-cyan-50/56">{agentAnalysis.questions[0]}</div>
) : null}
</div>
) : (
<div className="mt-2 rounded-md border border-white/10 bg-black/24 px-2.5 py-2 text-[10px] leading-snug text-white/42">
/
</div>
)}
{agentTraits.length ? (
<div className="mt-2 flex flex-wrap gap-1">
{agentTraits.map((trait) => {
const selected = selectedAgentTraits.includes(trait)
return (
<button
key={trait}
type="button"
onClick={() => toggleSubjectAgentTrait(trait)}
className={`h-6 rounded-full border px-2 text-[9.5px] transition ${
selected
? "border-[#d6b36a]/70 bg-[#d6b36a]/16 text-white"
: "border-white/10 bg-black/28 text-white/52 hover:border-[#d6b36a]/50 hover:text-white"
}`}
>
{trait}
</button>
)
})}
</div>
) : null}
<div className="mt-2 max-h-32 space-y-1.5 overflow-y-auto rounded-md border border-white/10 bg-black/24 p-2">
{agentMessages.length ? agentMessages.slice(-6).map((message, index) => (
<div key={`${message.created_at}-${index}`} className={`rounded px-2 py-1.5 text-[10px] leading-snug ${
message.role === "user" ? "ml-6 bg-white/[0.07] text-white/70" : "mr-6 bg-cyan-300/[0.07] text-cyan-50/68"
}`}>
{message.content}
</div>
)) : (
<div className="flex h-16 items-center justify-center text-center text-[10px] leading-snug text-white/32">
</div>
)}
</div>
<textarea
value={agentInput}
onChange={(event) => {
setAgentInput(event.target.value)
setAgentRequirement(event.target.value || job.subject_agent?.requirements_zh || "")
}}
placeholder="例如:保留透明骨骼男孩和蓝色头带,人物占画面 90%,服装每张完全一致,生成 6 张"
rows={2}
className="mt-2 min-h-[56px] w-full resize-none rounded-md border border-white/10 bg-black/35 px-2.5 py-2 text-[10.5px] leading-snug text-white outline-none placeholder:text-white/28 focus:border-cyan-300/50"
/>
<div className="mt-2 grid grid-cols-[1fr_auto] gap-1.5">
<button
type="button"
onClick={() => void sendSubjectAgentRequirement()}
disabled={subjectAgentBusy === "message"}
className="inline-flex h-8 items-center justify-center gap-1 rounded-md border border-white/10 bg-white/[0.06] px-3 text-[10.5px] font-semibold text-white/68 transition hover:border-white/25 hover:text-white disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectAgentBusy === "message" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
</button>
<button
type="button"
onClick={() => void generateSubjectPack(agentMode)}
disabled={subjectBusy || agentModeRunning || !canGenerateAgentPack}
className="skg-primary-action inline-flex h-8 items-center justify-center gap-1 px-3 text-[10.5px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectBusyFor?.mode === agentMode || agentModeRunning ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkles className="h-3.5 w-3.5" />}
{selectedSubjectViews.length}
</button>
</div>
{lastSubjectProfile ? (
<div className="mt-2 rounded border border-cyan-200/16 bg-cyan-300/[0.055] px-2 py-1.5 text-[9.5px] leading-snug text-cyan-50/62">
{lastSubjectProfile.summary}
</div>
) : null}
<div className="flex min-h-[410px] items-center justify-center rounded-md border border-dashed border-white/12 bg-black/18 p-4 2xl:min-h-[500px]">
<span className="text-[10.5px] text-white/28"></span>
</div>
</div>