fix: split generation workspace panels

This commit is contained in:
2026-05-20 10:01:21 +08:00
parent 27c8ce3819
commit afe5e8bfdf
5 changed files with 142 additions and 47 deletions

View File

@@ -1489,6 +1489,20 @@
"message": "fix: loosen glass dashboard workspace", "message": "fix: loosen glass dashboard workspace",
"hash": "7fcda19", "hash": "7fcda19",
"files_changed": 8 "files_changed": 8
},
{
"ts": "2026-05-20T09:49:16+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 09:49 (~3)",
"hash": "ccbfd3e",
"files_changed": 3
},
{
"ts": "2026-05-20T09:54:43+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 09:54 (~3)",
"hash": "7ad323a",
"files_changed": 3
} }
] ]
} }

View File

@@ -354,6 +354,40 @@ input, textarea {
backdrop-filter: blur(18px); backdrop-filter: blur(18px);
} }
.session-workspace {
height: calc(100vh - 116px);
min-height: 640px;
}
.session-split {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 430px);
gap: 18px;
align-items: stretch;
}
.session-image-pane,
.pack-scroll {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}
.session-image-pane::-webkit-scrollbar,
.pack-scroll::-webkit-scrollbar {
width: 8px;
}
.session-image-pane::-webkit-scrollbar-thumb,
.pack-scroll::-webkit-scrollbar-thumb {
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
}
.session-pack-pane {
border-left: 1px solid rgba(255, 255, 255, 0.08);
padding-left: 18px;
}
.result-grid { .result-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
@@ -368,6 +402,41 @@ input, textarea {
} }
} }
@media (min-width: 1760px) {
.session-split {
grid-template-columns: minmax(0, 1fr) minmax(400px, 480px);
gap: 22px;
}
.session-pack-pane {
padding-left: 22px;
}
}
@media (max-width: 1180px) {
.session-workspace {
height: auto;
min-height: 0;
}
.session-split {
display: flex;
flex-direction: column;
}
.session-image-pane,
.session-pack-pane {
overflow: visible;
}
.session-pack-pane {
border-left: 0;
border-top: 1px solid rgba(255, 255, 255, 0.08);
padding-left: 0;
padding-top: 18px;
}
}
/* ===== Login (cloned from SKG source) ===== */ /* ===== Login (cloned from SKG source) ===== */
.login-page { .login-page {
background: background:

View File

@@ -353,8 +353,8 @@ export default function Home() {
/> />
)} )}
{current && ( {current && (
<section className="dashboard-workbench space-y-5 p-5"> <section className="dashboard-workbench session-workspace flex flex-col gap-5 p-5">
<div className="flex items-end justify-between gap-4"> <div className="flex shrink-0 items-end justify-between gap-4">
<div> <div>
<span className="section-eyebrow">Step · 02 · Quick Screen</span> <span className="section-eyebrow">Step · 02 · Quick Screen</span>
<h2 className="mt-2 text-lg font-semibold text-white"></h2> <h2 className="mt-2 text-lg font-semibold text-white"></h2>
@@ -364,19 +364,25 @@ export default function Home() {
</div> </div>
<code className="max-w-[220px] truncate text-[11px] text-white/30 font-mono">{current.id}</code> <code className="max-w-[220px] truncate text-[11px] text-white/30 font-mono">{current.id}</code>
</div> </div>
<ResultGrid images={current.images} onAction={handleAction} /> <div className="session-split min-h-0 flex-1">
<PackPanel <div className="session-image-pane min-h-0 overflow-y-auto pr-1">
session={current} <ResultGrid images={current.images} onAction={handleAction} />
loadingKind={loadingKind} </div>
allLoading={allLoading} <aside className="session-pack-pane min-h-0 overflow-hidden">
characterLoading={characterLoading} <PackPanel
videoLoading={videoLoading} session={current}
onGenerate={handleGeneratePack} loadingKind={loadingKind}
onGenerateAll={handleGenerateAll} allLoading={allLoading}
onLockCharacter={handleLockCharacter} characterLoading={characterLoading}
onRegenerateAsset={handleRegenerateAsset} videoLoading={videoLoading}
onGenerateVideo={handleGenerateVideo} onGenerate={handleGeneratePack}
/> onGenerateAll={handleGenerateAll}
onLockCharacter={handleLockCharacter}
onRegenerateAsset={handleRegenerateAsset}
onGenerateVideo={handleGenerateVideo}
/>
</aside>
</div>
</section> </section>
)} )}
</div> </div>

View File

@@ -60,10 +60,10 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
} }
} }
return ( return (
<div className="grid grid-cols-[96px_minmax(0,1fr)_minmax(132px,auto)] gap-3 p-3 rounded-[8px] bg-white/[0.035] ring-1 ring-white/[0.06] hover:bg-white/[0.05] hover:ring-white/[0.12] transition-all"> <div className="grid grid-cols-[72px_minmax(0,1fr)_minmax(78px,auto)] gap-3 p-3 rounded-[8px] bg-white/[0.035] ring-1 ring-white/[0.06] hover:bg-white/[0.05] hover:ring-white/[0.12] transition-all 2xl:grid-cols-[84px_minmax(0,1fr)_minmax(104px,auto)]">
{/* thumbnail */} {/* thumbnail */}
<div <div
className="relative flex w-[92px] max-h-[110px] items-center justify-center overflow-visible rounded-[8px] bg-black/30 ring-1 ring-white/[0.08]" className="relative flex w-[70px] max-h-[86px] items-center justify-center overflow-visible rounded-[8px] bg-black/30 ring-1 ring-white/[0.08] 2xl:w-[82px] 2xl:max-h-[98px]"
style={{ aspectRatio: aspectCss(asset?.aspectRatio ?? template.aspectRatio) }} style={{ aspectRatio: aspectCss(asset?.aspectRatio ?? template.aspectRatio) }}
> >
{ready ? ( {ready ? (
@@ -124,7 +124,7 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
{ready && onRegenerate && ( {ready && onRegenerate && (
<button <button
onClick={() => setShowRedo(value => !value)} onClick={() => setShowRedo(value => !value)}
className="mt-1 rounded-[8px] bg-white/[0.055] px-2.5 py-1.5 text-[10px] text-white/60 ring-1 ring-white/[0.08] transition-colors hover:bg-white/[0.10] hover:text-white" className="mt-1 rounded-[8px] bg-white/[0.055] px-2 py-1.5 text-[10px] text-white/60 ring-1 ring-white/[0.08] transition-colors hover:bg-white/[0.10] hover:text-white 2xl:px-2.5"
> >
{showRedo ? '收起重做' : '重做'} {showRedo ? '收起重做' : '重做'}
</button> </button>
@@ -475,9 +475,13 @@ export default function PackPanel({
if (!primaryImage) { if (!primaryImage) {
return ( return (
<section className="card p-6"> <section className="card h-full p-6">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="w-8 h-8 rounded-[8px] bg-white/[0.05] border border-white/[0.08] flex items-center justify-center text-white/30 text-sm"></div> <div className="w-8 h-8 rounded-[8px] bg-white/[0.05] border border-white/[0.08] flex items-center justify-center text-white/30 text-sm">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" aria-hidden="true">
<path d="M4 7h16M7 4v16M17 4v16M4 17h16" strokeLinecap="round" />
</svg>
</div>
<div> <div>
<span className="section-eyebrow">Step · 03 · Assets</span> <span className="section-eyebrow">Step · 03 · Assets</span>
<h2 className="mt-1.5 text-sm font-semibold text-white"></h2> <h2 className="mt-1.5 text-sm font-semibold text-white"></h2>
@@ -493,10 +497,10 @@ export default function PackPanel({
const generatedTotal = packs.reduce((s, p) => s + p.assets.length, 0); const generatedTotal = packs.reduce((s, p) => s + p.assets.length, 0);
return ( return (
<div className="space-y-4"> <div className="flex h-full min-h-0 flex-col gap-4">
{/* Step 03 header card */} {/* Step 03 header card */}
<section className="card p-5 space-y-4"> <section className="card shrink-0 space-y-4 p-4">
<div className="flex items-start justify-between gap-4"> <div className="flex flex-col gap-4 2xl:flex-row 2xl:items-start 2xl:justify-between">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<span className="section-eyebrow">Step · 03 · Lock & Generate</span> <span className="section-eyebrow">Step · 03 · Lock & Generate</span>
<h2 className="mt-1.5 text-base font-semibold text-white"> & </h2> <h2 className="mt-1.5 text-base font-semibold text-white"> & </h2>
@@ -505,7 +509,7 @@ export default function PackPanel({
</p> </p>
</div> </div>
{/* primary image + stats */} {/* primary image + stats */}
<div className="flex items-center gap-4 shrink-0"> <div className="flex items-center justify-between gap-4 shrink-0 2xl:justify-end">
<div className="text-right text-[11px] text-white/40 space-y-0.5"> <div className="text-right text-[11px] text-white/40 space-y-0.5">
<div className="font-mono text-white/70">{generatedTotal} <span className="text-white/30">/ {totalImageSlots} </span></div> <div className="font-mono text-white/70">{generatedTotal} <span className="text-white/30">/ {totalImageSlots} </span></div>
<div className="text-[10px]"></div> <div className="text-[10px]"></div>
@@ -519,11 +523,11 @@ export default function PackPanel({
</div> </div>
{/* action buttons */} {/* action buttons */}
<div className="flex flex-wrap items-center gap-2"> <div className="grid grid-cols-1 gap-2 2xl:grid-cols-2">
<button <button
onClick={() => onLockCharacter(primaryImage)} onClick={() => onLockCharacter(primaryImage)}
disabled={characterLoading || !!loadingKind || allLoading} disabled={characterLoading || !!loadingKind || allLoading}
className="btn btn-glass text-xs disabled:opacity-40" className="btn btn-glass justify-center text-xs disabled:opacity-40"
> >
{characterLoading ? ( {characterLoading ? (
<svg width="12" height="12" viewBox="0 0 24 24" className="animate-spin" fill="none" stroke="currentColor" strokeWidth="2.5"> <svg width="12" height="12" viewBox="0 0 24 24" className="animate-spin" fill="none" stroke="currentColor" strokeWidth="2.5">
@@ -543,7 +547,7 @@ export default function PackPanel({
if (ok) onGenerateAll(primaryImage); if (ok) onGenerateAll(primaryImage);
}} }}
disabled={allLoading || !!loadingKind || characterLoading} disabled={allLoading || !!loadingKind || characterLoading}
className="btn btn-primary text-xs disabled:opacity-40" className="btn btn-primary justify-center text-xs disabled:opacity-40"
> >
{allLoading ? ( {allLoading ? (
<> <>
@@ -590,26 +594,28 @@ export default function PackPanel({
<SectionNav active={activeNav} onChange={setActiveNav} /> <SectionNav active={activeNav} onChange={setActiveNav} />
</section> </section>
{/* Pack sections */} <div className="pack-scroll min-h-0 flex-1 space-y-3 overflow-y-auto pr-1">
{PACK_ORDER.map(kind => { {/* Pack sections */}
const pack = packs.find(p => p.kind === kind && p.sourceImageId === primaryImage.id); {PACK_ORDER.map(kind => {
return ( const pack = packs.find(p => p.kind === kind && p.sourceImageId === primaryImage.id);
<PackSection return (
key={kind} <PackSection
kind={kind} key={kind}
session={session} kind={kind}
primaryImage={primaryImage} session={session}
pack={pack} primaryImage={primaryImage}
isLoading={loadingKind === kind} pack={pack}
onGenerate={() => onGenerate(primaryImage, kind)} isLoading={loadingKind === kind}
onRegenerateAsset={onRegenerateAsset} onGenerate={() => onGenerate(primaryImage, kind)}
/> onRegenerateAsset={onRegenerateAsset}
); />
})} );
})}
{/* Text + Video */} {/* Text + Video */}
<TextTemplateSection /> <TextTemplateSection />
<VideoSection videoLoading={videoLoading} primaryImage={primaryImage} onGenerateVideo={onGenerateVideo} /> <VideoSection videoLoading={videoLoading} primaryImage={primaryImage} onGenerateVideo={onGenerateVideo} />
</div>
</div> </div>
); );
} }

View File

@@ -46,7 +46,7 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="sticky top-0 z-20 -mx-1 flex flex-wrap items-center justify-between gap-3 rounded-[8px] bg-[#202020]/82 px-1 py-2 backdrop-blur-xl">
<div className="flex items-center gap-2 text-[11px] text-white/55"> <div className="flex items-center gap-2 text-[11px] text-white/55">
<kbd className="kbd">1</kbd> <kbd className="kbd">1</kbd>
<span className="text-white/40"></span> <span className="text-white/40"></span>