完善视频提炼和视频复刻完善提交测试
This commit is contained in:
@@ -888,9 +888,33 @@ export function PipelinePage(props: {
|
||||
delBusy(busyKey);
|
||||
}
|
||||
}
|
||||
// 流程步骤4 · 生成人物立绘(App 层自动接力三视图)
|
||||
async function genPersonPortrait(prompt: string, label: string | undefined, busyKey: string) {
|
||||
return await genBaseAsset("person", prompt, label, busyKey);
|
||||
// 流程步骤4 · 生成人物立绘 → 出图后自动接力三视图(用户不必再进详情弹窗手点「AI 生成三视图」;
|
||||
// 弹窗里那个按钮保留,用于重跑 / 补生成)。后端 generate-base-asset 已带 auto_triview 自动接力,
|
||||
// 这里只做「保持转圈 + 兜底」:立绘落库后先确认后端已把三视图任务排上,排上了就把 loading 交给
|
||||
// pending-assets 轮询;真没排上(旧后端 / 接力异常)才补发一次,避免重复出图重复扣费。
|
||||
async function genPersonPortrait(prompt: string, label: string | undefined, busyKey: string, referenceAssetId?: string) {
|
||||
const res = await genBaseAsset("person", prompt, label, busyKey, referenceAssetId);
|
||||
const portraitId = res?.adopted_asset || "";
|
||||
if (!portraitId) return res;
|
||||
// 立绘的 busy 已在 genBaseAsset 里落回,这里立刻把三视图的 busy 顶上,卡片不闪「已就绪」
|
||||
const triKeys = [...new Set([busyKey, ...entityGenKeys("person", label)])].map((k) => `${k}:tri`);
|
||||
triKeys.forEach(addBusy);
|
||||
try {
|
||||
let chained = false;
|
||||
for (let i = 0; i < 3 && !chained; i += 1) {
|
||||
try {
|
||||
const pending = (await api.pendingAssets(project.id)).pending || [];
|
||||
chained = pending.some((p) => p.is_triview && p.triview_of === portraitId);
|
||||
} catch {
|
||||
/* 网络抖动:下一轮再看 */
|
||||
}
|
||||
if (!chained && i < 2) await new Promise((resolve) => window.setTimeout(resolve, 3000));
|
||||
}
|
||||
if (!chained) await onGenerateTriview(portraitId);
|
||||
} finally {
|
||||
triKeys.forEach(delBusy);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
// ── 流程步骤4 · 实体提取闸门(进资产趴入口):不自动花钱,用户点按钮才提取/生成 ──
|
||||
// idle=露三按钮 / running=露 loading;提取后 metadata.script_entities 落库、或已有资产 → 闸门由渲染层自动隐藏
|
||||
@@ -906,8 +930,7 @@ export function PipelinePage(props: {
|
||||
for (const e of entities) {
|
||||
const bk = `seed:${e.type === "character" ? "person" : "scene"}:${e.name}`;
|
||||
if (e.type === "character") {
|
||||
if (mode === "full") await genPersonPortrait(e.visual_prompt, e.name, bk);
|
||||
else await genBaseAsset("person", e.visual_prompt, e.name, bk);
|
||||
await genPersonPortrait(e.visual_prompt, e.name, bk); // 立绘出完自动接力三视图
|
||||
} else {
|
||||
await genBaseAsset("scene", e.visual_prompt, e.name, bk);
|
||||
}
|
||||
@@ -2944,6 +2967,7 @@ export function PipelinePage(props: {
|
||||
if (activeDot !== 2 && viewStage !== 2) return;
|
||||
let stopped = false;
|
||||
let timer = 0;
|
||||
let idleTicks = 0; // 连续「无在途」次数:立绘刚出片、后端接力的三视图任务可能晚半拍才入库,别一空就停
|
||||
const tick = async () => {
|
||||
if (document.hidden) { if (!stopped) timer = window.setTimeout(tick, 4000); return; } // 后台不空跑(纯读),保留心跳回前台即恢复
|
||||
try {
|
||||
@@ -2961,7 +2985,9 @@ export function PipelinePage(props: {
|
||||
prevPendingIdsRef.current = list.map((pending) => pending.id);
|
||||
setPendingGen(list.map((p) => ({ kind: p.kind, label: p.label, is_triview: p.is_triview, triview_of: p.triview_of })));
|
||||
// 没有在途出图、本地也没有生成在跑 → 没什么可等,停轮询;再点生成(genBusy 变)时本 effect 会重订阅恢复。
|
||||
if (list.length === 0 && genBusy.size === 0) { stopped = true; return; }
|
||||
// 连空两轮(~8s)才停:立绘落库瞬间后端才接力建三视图任务,一空就停会把「三视图生成中」整条漏掉。
|
||||
idleTicks = list.length === 0 && genBusy.size === 0 ? idleTicks + 1 : 0;
|
||||
if (idleTicks >= 2) { stopped = true; return; }
|
||||
} catch {
|
||||
/* 忽略,下一轮再试 */
|
||||
}
|
||||
@@ -3722,7 +3748,7 @@ export function PipelinePage(props: {
|
||||
{kind === "scene" ? <Image /> : <UsersRound />}
|
||||
<span>{kind === "scene" ? "场景库替换" : "模特库替换"}</span>
|
||||
</button>
|
||||
<button className="as-ai-btn" type="button" data-stop disabled={busy} onClick={() => { const p = (assetPromptDraft[seedKey] ?? tagPrompt(tag)).trim() || tagPrompt(tag); void genBaseAsset(kind, p, tag, seedKey); }}>
|
||||
<button className="as-ai-btn" type="button" data-stop disabled={busy} onClick={() => { const p = (assetPromptDraft[seedKey] ?? tagPrompt(tag)).trim() || tagPrompt(tag); void (kind === "person" ? genPersonPortrait(p, tag, seedKey) : genBaseAsset(kind, p, tag, seedKey)); }}>
|
||||
<Sparkles /><span>{busy ? "生成中…" : "AI 生成"}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -3738,7 +3764,6 @@ export function PipelinePage(props: {
|
||||
const portraitBusy = genKeys.some((k) => isBusy(k)) || (!mainUrl && pendingHas(kind, entity.name));
|
||||
const triBusy = kind === "person" && (genKeys.some((k) => isBusy(`${k}:tri`)) || pendingTriFor(kind, entity));
|
||||
const busy = portraitBusy || triBusy;
|
||||
const loadingText = kind === "person" && mainUrl ? "三视图生成中…" : "生成中…";
|
||||
const rs = (kind === "person" || kind === "scene") && grp.adopted_asset ? (reviews[grp.adopted_asset] || grp.adopted_asset_review || "") : "";
|
||||
const previewState = `${mainUrl ? " ready" : busy ? " generating" : " pending"}${busy ? " generating" : ""}`;
|
||||
return (
|
||||
@@ -3763,7 +3788,7 @@ export function PipelinePage(props: {
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openAssetDetail(kind, entity); } }}
|
||||
>
|
||||
{kind === "person" && mainUrl ? <img className="as-gen-photo" src={mainUrl} alt="" /> : null}
|
||||
{busy ? <span className={`asset-card-loading${mainUrl ? " veil" : ""}`}><span className="asset-spinner" aria-hidden="true"></span><span>{loadingText}</span></span> : null}
|
||||
{busy ? <span className={`asset-card-loading${mainUrl ? " veil" : ""}`}><span className="asset-spinner" aria-hidden="true"></span><span>生成中…</span></span> : null}
|
||||
</div>
|
||||
<div className="as-gen-meta">
|
||||
<h4 onClick={() => openAssetDetail(kind, entity)}>{entity.name}</h4>
|
||||
@@ -3789,7 +3814,7 @@ export function PipelinePage(props: {
|
||||
{kind === "scene" ? <Image /> : <UsersRound />}
|
||||
<span>{kind === "scene" ? "场景库替换" : "模特库替换"}</span>
|
||||
</button>
|
||||
<button className="as-ai-btn" type="button" data-stop disabled={busy} onClick={() => { const p = (assetPromptDraft[entity.key] ?? grp.prompt ?? "").trim() || genPrompt; const ref = kind === "person" && grp.adopted_asset ? grp.adopted_asset : undefined; void genBaseAsset(kind, p, entity.name, entBK, ref); }}>
|
||||
<button className="as-ai-btn" type="button" data-stop disabled={busy} onClick={() => { const p = (assetPromptDraft[entity.key] ?? grp.prompt ?? "").trim() || genPrompt; const ref = kind === "person" && grp.adopted_asset ? grp.adopted_asset : undefined; void (kind === "person" ? genPersonPortrait(p, entity.name, entBK, ref) : genBaseAsset(kind, p, entity.name, entBK)); }}>
|
||||
<Sparkles /><span>{busy ? "生成中…" : mainUrl ? "重新生成" : "AI 生成"}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -4743,7 +4768,8 @@ export function PipelinePage(props: {
|
||||
// 重跑立绘 → 追加新立绘并自动接力该版三视图。
|
||||
// 角色重跑:若该角色已有当前立绘,传它作参考图 → 后端走 image_edit 参考立绘+提示词,保持同一人物一致(不重抽随机人)。
|
||||
const ref = isPerson && viewPortraitAsset ? viewPortraitAsset : undefined;
|
||||
await genBaseAsset(isPerson ? "person" : "scene", prompt, entity!.name, pBK, ref);
|
||||
if (isPerson) await genPersonPortrait(prompt, entity!.name, pBK, ref);
|
||||
else await genBaseAsset("scene", prompt, entity!.name, pBK);
|
||||
setAdPortraitId(null); setAdTriId(null);
|
||||
}
|
||||
async function regenTri() {
|
||||
@@ -4770,7 +4796,7 @@ export function PipelinePage(props: {
|
||||
{/* 同三视图:大图随数据(portraitUrl)走,出图完成即显示,不被在途 busyPortrait 卡转圈 */}
|
||||
<div className={`placeholder ad-lead-img${portraitUrl ? " has-mock-media" : ""}`} style={portraitUrl ? mediaStyle(portraitUrl) : undefined}>
|
||||
{!portraitUrl && (busyPortrait
|
||||
? <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}><span className="spinner" aria-hidden="true"></span><span className="ph-frame">立绘生成中…</span></div>
|
||||
? <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}><span className="spinner" aria-hidden="true"></span><span className="ph-frame">生成中…</span></div>
|
||||
: <span className="ph-frame">立绘</span>)}
|
||||
</div>
|
||||
{portraitUrl && <button className="ad-zoom-btn" type="button" title="查看大图" onClick={() => setPreview({ src: portraitUrl, kind: "image", name: `${entity.name} · 立绘` })}>{zoomSvg}</button>}
|
||||
@@ -4805,7 +4831,7 @@ export function PipelinePage(props: {
|
||||
避免「缩略图已出、大图还在转圈」——busyTri 只在「还没任何结果」时显示生成中占位 */}
|
||||
<div className={`placeholder${triUrl ? " has-mock-media" : ""}`} style={triUrl ? mediaStyle(triUrl) : undefined}>
|
||||
{!triUrl && (busyTri
|
||||
? <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}><span className="spinner" aria-hidden="true"></span><span className="ph-frame">三视图生成中…</span></div>
|
||||
? <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}><span className="spinner" aria-hidden="true"></span><span className="ph-frame">生成中…</span></div>
|
||||
: <span className="ph-frame">正 / 侧 / 背 · 三视图</span>)}
|
||||
{triUrl && busyTri && <span className="sb-gen-veil" aria-hidden="true"><span className="spinner" /></span>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user