diff --git a/core/frontend/src/pipeline-page.css b/core/frontend/src/pipeline-page.css index 5191e8a..89c63cc 100644 --- a/core/frontend/src/pipeline-page.css +++ b/core/frontend/src/pipeline-page.css @@ -385,6 +385,8 @@ .content--fh-flat .stage[data-stage-pane="3"].active > .stage-storyboard > .sb-canvas > .sb-main-img { width: 100%; } /* 有真图时大图占满画布整个高度(不再 16/9 居中留上下空白) */ .content--fh-flat .stage[data-stage-pane="3"].active > .stage-storyboard > .sb-canvas > .sb-main-img.has-mock-media { align-self: stretch; aspect-ratio: auto; } + /* 2:3 空占位:别被上面的 width:100% 撑成超高满框 —— 给个合理高度、按 2:3 推宽、居中 */ + .content--fh-flat .stage[data-stage-pane="3"].active > .stage-storyboard > .sb-canvas > .sb-main-img:not(.has-mock-media) { width: auto; height: min(70vh, 540px); aspect-ratio: 2/3; justify-self: center; } .content--fh-flat .stage[data-stage-pane="3"].active > .stage-storyboard > .sb-side { display: flex; flex-direction: column; min-height: 0; } .content--fh-flat .stage[data-stage-pane="3"].active > .stage-storyboard > .sb-side > .pane { flex: 1 1 0; min-height: 0; overflow-y: auto; border: 0; border-radius: 0; background: var(--surface); padding: 18px 28px; } .content--fh-flat .stage[data-stage-pane="3"].active .sb-scenes-col { max-height: none; } @@ -400,10 +402,15 @@ .sb-scene-thumb .placeholder { aspect-ratio: 1; } .sb-scene-thumb .nm { font-size: 11.5px; font-weight: 500; color: var(--accent-black); } .sb-scene-thumb .sub { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); } - .sb-main-img { aspect-ratio: 16/9; min-height: 0; } + /* 整张故事板出图是竖屏 2:3(1024×1536),空占位也用 2:3,跟成品同形(不再 16:9 横框) */ + .sb-main-img { aspect-ratio: 2/3; min-height: 0; } /* 整张故事板是竖长拼图,cover 会裁掉上下 —— 用 contain 完整显示; 留白底色用白(--surface)而非占位浅灰,避免「灰底大框」(对齐 HTML 干净白底) */ .sb-main-img.has-mock-media { background-size: contain; background-color: var(--surface); } + /* 整张生成中:每张分镜占位上叠一层半透明遮罩 + 转圈 spinner(不全屏遮挡;样式同基础资产趴) */ + .sb-gen-veil { position: absolute; inset: 0; z-index: 3; display: grid; place-items: center; border-radius: inherit; pointer-events: none; background: color-mix(in srgb, var(--surface) 62%, transparent); } + .sb-scene-thumb .sb-gen-veil .spinner { width: 16px; height: 16px; } + .sb-main-img .sb-gen-veil .spinner { width: 28px; height: 28px; } .sb-rerun-note { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; margin-bottom: 14px; background: rgba(180,83,9,.08); border: 1px solid rgba(180,83,9,.20); border-radius: var(--r-md); color: #7C3A05; line-height: 1.55; } .sb-rerun-note .warn-ic { width: 22px; height: 22px; border-radius: var(--r-sm); background: rgba(180,83,9,.12); color: #B45309; display: grid; place-items: center; flex: 0 0 22px; } diff --git a/core/frontend/src/routes/pipeline.tsx b/core/frontend/src/routes/pipeline.tsx index 386f967..468640f 100644 --- a/core/frontend/src/routes/pipeline.tsx +++ b/core/frontend/src/routes/pipeline.tsx @@ -481,7 +481,7 @@ export function PipelinePage(props: { onGenerateTriview: (portraitGroupId: string) => Promise<{ id: string } | null>; // 流程步骤4 · 添加人物工作台命名:把名字写回该人物资产 onRenameActor?: (assetId: string, name: string) => Promise; - onGenerateStoryboard: (prompt: string) => void; + onGenerateStoryboard: (prompt: string) => void | Promise; onSkipStoryboard: () => Promise; onSubmitVideo: (segmentId: string, prompt: string) => void; onSubmitAllVideos: (prompt: string) => void; @@ -846,6 +846,8 @@ export function PipelinePage(props: { // 让用户一眼看出「这里本该有几张」,出图后逐个填真图。无采用版脚本则为 0(显示「暂无」)。 const sbAdoptedScript = scripts.find((s) => s.is_adopted) || null; const sbExpectedShots = sbAdoptedScript ? (sbAdoptedScript.segments?.length ?? 0) : 0; + // 整张故事板生成中:不全屏遮挡,只在每张分镜占位上转个 spinner(样式同基础资产趴) + const [sbGenerating, setSbGenerating] = useState(false); // 没生成故事板时点「确认故事板」→ 弹气泡提示先去生成(3.5s 自动收起) const [sbConfirmHint, setSbConfirmHint] = useState(false); useEffect(() => { @@ -2943,7 +2945,7 @@ export function PipelinePage(props: { const url = frameUrl(frame); return (
setSbSelected(idx)}> -
场 {idx + 1}
+
场 {idx + 1}{sbGenerating &&
场 {idx + 1}
{times[idx] || `#${frame.sort_order + 1}`}
@@ -2953,7 +2955,7 @@ export function PipelinePage(props: { /* 还没出图:按采用版脚本镜头数铺等量空占位,标「待生成」,让用户看出本该有几张 */ Array.from({ length: sbExpectedShots }, (_, idx) => (
setSbSelected(idx)}> -
场 {idx + 1}
+
场 {idx + 1}{sbGenerating &&
场 {idx + 1}
// 待生成
@@ -2965,6 +2967,7 @@ export function PipelinePage(props: { return (
setPreview({ src: url, kind: "image", name: `场 ${sbSelected + 1}` }) : undefined} onKeyDown={url ? (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setPreview({ src: url, kind: "image", name: `场 ${sbSelected + 1}` }); } } : undefined}> {sbActiveFrame ? `场 ${sbSelected + 1}` : sbExpectedShots ? `场 ${sbSelected + 1} · 待生成` : "// 故事板未生成"} + {sbGenerating &&
); })()} @@ -2999,7 +3002,7 @@ export function PipelinePage(props: { onChange={(v) => setStoryboardPrompt(v.trim())} />
-