fix: 清单4 未完成项二轮(视频项目收口/选用角色/场景空镜/场景刷新 + 4:5 尺寸)

ZWQ#15 视频进度卡 3/4(真因补全):
- current_stage 没推到 VIDEO 时 settle 守卫直接返回 → 永不收口。
  submit/poll/adopt 幂等补推 current_stage→VIDEO;进详情(retrieve)检测全段已出片即自愈收口。
  存量卡死项目一打开就自动变「已完成」。+2 测试

ZWQ#7 演员库选用角色未自动「已过审/已采用」:
- 真实流程是 attach(选用),非详情弹窗按钮。attach 时显式置 adopt=adopted +
  人物立绘自动送审(on_commit)。+1 测试

ZWQ#13 场景出真人致视频被拒(加强):
- 场景空镜约束改为显式压过脚本描述里的人物词(「即使上文提到人也只画环境」)。

ZWQ#17 场景生成中刷新跳新窗口:
- 正在生成的场景草稿不再落盘 → 生成中刷新页面不会与新实体卡并存成两个窗口。

附:平台套图 4:5 尺寸 1664x2080→1728x2160(≥Seedream 5.0 最小面积 3686400px,否则 ARK 退 400)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-27 17:03:17 +08:00
co-authored by Claude Opus 4.8
parent 98b6ba948e
commit 00e606a6a6
4 changed files with 101 additions and 18 deletions
+14 -4
View File
@@ -614,11 +614,14 @@ export function PipelinePage(props: {
// 出图成功后从草稿列表移除(生成结果会作为实体卡出现在下方)。不直接生成,避免一点就出一张默认图。
// 持久化到 localStorage(按项目隔离):新增场景没点 AI 生成就刷新页面也不会丢(ZWQ#10)。
const sceneDraftsKey = `airshelf:scenedrafts:${project.id}`;
const [sceneDrafts, setSceneDrafts] = useState<Array<{ id: string; title: string; prompt: string }>>(() => {
try { const raw = localStorage.getItem(sceneDraftsKey); return raw ? (JSON.parse(raw) as Array<{ id: string; title: string; prompt: string }>) : []; } catch { return []; }
type SceneDraft = { id: string; title: string; prompt: string; generating?: boolean };
const [sceneDrafts, setSceneDrafts] = useState<SceneDraft[]>(() => {
try { const raw = localStorage.getItem(sceneDraftsKey); return raw ? (JSON.parse(raw) as SceneDraft[]).map((d) => ({ ...d, generating: false })) : []; } catch { return []; }
});
useEffect(() => {
try { localStorage.setItem(sceneDraftsKey, JSON.stringify(sceneDrafts)); } catch { /* localStorage 不可用时静默降级 */ }
// 只持久化「还没点 AI 生成」的草稿;正在生成的草稿不落盘——否则生成中刷新页面后,
// 这张草稿会和生成完成后新出现的实体卡并存成两个窗口(ZWQ#17)。
try { localStorage.setItem(sceneDraftsKey, JSON.stringify(sceneDrafts.filter((d) => !d.generating))); } catch { /* localStorage 不可用时静默降级 */ }
}, [sceneDrafts, sceneDraftsKey]);
// 行39 · 商品三视图:点「AI 生成三视图」展开卡片右侧预览面板(对齐原版 prod-preview,非弹窗)
// 每次生成=一个 product group=一版三视图;预览态(triPreviewId)只切主图,采用态存 metadata.product_tri_group
@@ -3029,7 +3032,14 @@ export function PipelinePage(props: {
<div className="hstack" style={{ marginTop: 10 }}>
<button className="btn btn-ghost btn-sm" type="button" data-stop disabled={busy} onClick={() => setSceneDrafts((list) => list.filter((d) => d.id !== draft.id))}></button>
<span className="spacer"></span>
<button className="btn btn-primary btn-sm" type="button" data-stop disabled={busy} onClick={async () => { const p = draft.prompt.trim() || genPrompt; const res = await genBaseAsset("scene", p, draft.title.trim() || undefined, dk); if (res && (res.id || res.adopted_asset)) setSceneDrafts((list) => list.filter((d) => d.id !== draft.id)); }}>{busy ? "生成中…" : "AI 生成"}</button>
<button className="btn btn-primary btn-sm" type="button" data-stop disabled={busy} onClick={async () => {
const p = draft.prompt.trim() || genPrompt;
// 一启动就标 generating(此后不再落盘):生成中刷新页面不会留下这张草稿 → 不会和新实体卡并存(ZWQ#17)
setSceneDrafts((list) => list.map((d) => d.id === draft.id ? { ...d, generating: true } : d));
const res = await genBaseAsset("scene", p, draft.title.trim() || undefined, dk);
if (res && (res.id || res.adopted_asset)) setSceneDrafts((list) => list.filter((d) => d.id !== draft.id)); // 成功:结果已成实体卡,移除草稿
else setSceneDrafts((list) => list.map((d) => d.id === draft.id ? { ...d, generating: false } : d)); // 失败:恢复草稿可重试
}}>{busy ? "生成中…" : "AI 生成"}</button>
</div>
</div>
</div>