fix: split generation workspace panels
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user