From be4bc1c8f4e2370522835a301ea17814cec44346 Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Tue, 23 Jun 2026 02:07:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(pipeline):=20=E6=9C=AA=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9A=84=E4=BA=BA=E7=89=A9/=E5=9C=BA=E6=99=AF=E5=8D=A1?= =?UTF-8?q?=E5=8F=AF=E7=82=B9=E8=BF=9B=E8=AF=A6=E6=83=85(=E5=85=88?= =?UTF-8?q?=E7=AB=8B=E7=BB=98=E5=90=8E=E4=B8=89=E8=A7=86=E5=9B=BE)+=20?= =?UTF-8?q?=E5=8D=A1=E5=86=85=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A1=86=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A5=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - seed(未生成·来自脚本)卡:缩略图/名字可点 → 进资产详情弹窗(保留卡上内联「AI 生成」快捷, 两条路都能生成,A 方案)。详情弹窗支持「还没生成组」的空壳实体(用脚本名+提示词兜底), 生成立绘后刷新按同名 key 自动切回真实体。 - 顺序限制(本就在详情里,现对 seed 也生效):没立绘时「AI 生成三视图」禁用 + 提示 「请先生成左侧立绘」;立绘按钮首次显示「生成立绘」(非「重跑」),替换走 seed 路由。 - 卡内提示词框(.prompt-box,外层人物/场景卡)固定 5 行高、超长框内滚动,不再被长提示词撑高。 自检:复用现成详情弹窗与 .btn/.prompt-box 共享类,无裸色值/无重写共享类;单橙锚点不变。 验证:tsc + vite build 绿;无头(独立 sqlite 栈)真点 seed 卡 → 详情打开、三视图禁用+提示、 立绘按钮「生成立绘」、提示词框 115px 固定且超长可滚动、0 控制台错误(截图存档)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- core/frontend/src/pipeline-page.css | 3 ++- core/frontend/src/routes/pipeline.tsx | 33 ++++++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/core/frontend/src/pipeline-page.css b/core/frontend/src/pipeline-page.css index a4b4210..5191e8a 100644 --- a/core/frontend/src/pipeline-page.css +++ b/core/frontend/src/pipeline-page.css @@ -375,7 +375,8 @@ .prompt-box[contenteditable="true"] { cursor: text; outline: none; } .prompt-box[contenteditable="true"]:hover { border-color: var(--heat-20); } .prompt-box[contenteditable="true"]:focus { border-color: var(--heat); background: var(--surface); color: var(--accent-black); box-shadow: 0 0 0 3px var(--heat-12); } - .fail-icon { width: 28px; height: 28px; background: var(--accent-crimson); color: var(--accent-white); display: grid; place-items: center; font-weight: 600; font-size: 16px; border-radius: 50%; } + /* 外层人物/场景卡提示词框:固定 5 行高(行高 1.55 + 内边距/边框),超长不撑高、框内上下滚动 */ + .prompt-box { height: calc(5 * 1.55em + 22px); overflow-y: auto; box-sizing: border-box; } /* ============= STAGE 3 · 故事板 ============= */ .content--fh .stage[data-stage-pane="3"].active > .stage-storyboard { flex: 1 1 0; min-height: 0; overflow-y: auto; } diff --git a/core/frontend/src/routes/pipeline.tsx b/core/frontend/src/routes/pipeline.tsx index 060c2cb..386f967 100644 --- a/core/frontend/src/routes/pipeline.tsx +++ b/core/frontend/src/routes/pipeline.tsx @@ -799,6 +799,14 @@ export function PipelinePage(props: { setAdTriId(null); // 跟随当前立绘的最新三视图 setAdPrompt(entity.group.prompt ?? ""); } + // seed(未生成)卡也能点进详情:还没组,用脚本名(=key)+ 当前提示词开一个「空壳」详情。 + // 进去先生成立绘;三视图按既有逻辑(无立绘则禁用)天然挡住。生成完刷新 → 同 key(=名字)自动切到真实体。 + function openSeedDetail(kind: "person" | "scene", tag: string, promptValue: string) { + setAdDetail({ kind, key: tag }); + setAdPortraitId(null); + setAdTriId(null); + setAdPrompt(promptValue); + } // 流程步骤4 · 演员库覆盖层:replace 带要替换的目标(已生成卡=groupId;seed 占位卡还没组=kind+label); // browse 带 studio 直接进「添加人物工作台」 const [actorLib, setActorLib] = useState<{ mode: "browse" | "replace"; studio?: boolean; groupId?: string; seedKind?: "person" | "scene"; seedLabel?: string; addNew?: boolean } | null>(null); @@ -2819,13 +2827,14 @@ export function PipelinePage(props: { const busy = isBusy(seedKey) || isBusy(`${seedKey}:tri`) || pendingHas(kind, tag); return (
-
+
openSeedDetail(kind, tag, promptValue)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openSeedDetail(kind, tag, promptValue); } }}> {busy ?
生成中 · 约 8s
: {tag} · 待生成}
-
{tag}来自脚本
+
openSeedDetail(kind, tag, promptValue)}>{tag}来自脚本
setAssetPromptDraft((m) => ({ ...m, [seedKey]: v }))} />
@@ -3612,8 +3621,15 @@ export function PipelinePage(props: { {/* ── 流程步骤4 · 人物/场景详情弹窗:立绘 + 三视图(人物)+ 提示词重获 + 版本历史 + 应用到当前项目 ── */} {adDetail && (() => { const entities = buildEntities(adDetail.kind); - const entity = entities.find((e) => e.key === adDetail.key); - if (!entity) return null; + const realEntity = entities.find((e) => e.key === adDetail.key); + // seed(还没生成组)也能开详情:用脚本名(=key)+ 当前提示词兜个「空壳」实体,进去先生成立绘。 + // 生成完刷新 → buildEntities 按同 key(label=名字)收到真实组,自动切回真实体。 + const entity: AssetEntity = realEntity ?? { + key: adDetail.key, label: adDetail.key, name: adDetail.key, kind: adDetail.kind, + group: { id: "", kind: adDetail.kind, prompt: adPrompt, adopted_asset: null, candidate_assets: [] }, + versions: [], + }; + const isSeed = !realEntity; // 空壳态:还没立绘 const isPerson = adDetail.kind === "person"; const grp = entity.group; const portraitVersions = grp.candidate_assets ?? []; @@ -3626,7 +3642,8 @@ export function PipelinePage(props: { const viewTriAsset = (adTriId && triVersions.includes(adTriId)) ? adTriId : (triGroup?.adopted_asset || triVersions.at(-1) || ""); const triUrl = candUrl(triGroup, viewTriAsset); const pBK = `addet-portrait:${entity.key}`; - const busyPortrait = isBusy(pBK); + // 没立绘时(尤其 seed 首次生成),立绘在 worker 跑 ~30s,本地 isBusy 早已落回 → 用 pendingHas 兜底转圈 + const busyPortrait = isBusy(pBK) || (!portraitUrl && pendingHas(adDetail.kind, entity.name)); const busyTri = isBusy(`addet-tri:${entity.key}`) || isBusy(`${pBK}:tri`); async function regenPortrait() { const prompt = adPrompt.trim() || grp.prompt || `${entity!.name},9:16 竖屏`; @@ -3701,7 +3718,7 @@ export function PipelinePage(props: { {!triUrl && !busyTri && (
- 三视图据这版立绘生成,保证正/侧/背多角度一致。点下方按钮生成。 + {viewPortraitAsset ? "三视图据这版立绘生成,保证正/侧/背多角度一致。点下方按钮生成。" : "请先生成左侧「立绘」,有了立绘才能据它生成三视图。"}
)} @@ -3725,9 +3742,9 @@ export function PipelinePage(props: {
- +