fix: move selected session detail into sidebar
This commit is contained in:
135
src/app/page.tsx
135
src/app/page.tsx
@@ -22,92 +22,6 @@ import type {
|
|||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
import type { VIDEO_TEMPLATES } from '@/lib/templates';
|
import type { VIDEO_TEMPLATES } from '@/lib/templates';
|
||||||
|
|
||||||
function sessionModeLabel(mode?: GenSession['inputMode']) {
|
|
||||||
if (mode === 'remix') return '二创';
|
|
||||||
if (mode === 'replicate') return '复刻';
|
|
||||||
if (mode === 'extend') return '补全';
|
|
||||||
return '想法';
|
|
||||||
}
|
|
||||||
|
|
||||||
function sessionReferenceImages(session: GenSession) {
|
|
||||||
const uploaded = session.uploadedImages?.map(image => ({
|
|
||||||
url: image.url,
|
|
||||||
label: image.role === 'subject' ? '主体图' : image.role === 'reference' ? '参考图' : image.accessoryName || image.role,
|
|
||||||
})) ?? [];
|
|
||||||
if (uploaded.length) return uploaded;
|
|
||||||
return session.refImages.map((url, index) => ({ url, label: `参考 ${index + 1}` }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function CurrentSessionRail({ session }: { session: GenSession }) {
|
|
||||||
const refs = sessionReferenceImages(session);
|
|
||||||
const selectedCount = session.images.filter(image => image.status === 'selected').length;
|
|
||||||
const packCount = session.packs?.length ?? 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className="border-b border-[#8cb478]/12 bg-black/16 p-5 lg:border-b-0 lg:border-r">
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<span className="section-eyebrow">Step · 02 · Session</span>
|
|
||||||
<div className="text-[10px] text-white/34">当前项目信息放在这里</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5 rounded-[8px] border border-[#e6f578]/45 bg-[#e6f578]/10 p-3">
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
|
||||||
<div className="text-[10px] uppercase tracking-[0.14em] text-[#e6f578]/65">已选项目</div>
|
|
||||||
<span className="rounded-full border border-white/10 bg-black/28 px-2 py-0.5 text-[10px] text-white/50">
|
|
||||||
{sessionModeLabel(session.inputMode)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 line-clamp-5 text-sm font-semibold leading-relaxed text-white">
|
|
||||||
{session.characterSpec?.name || session.prompt || '未填写'}
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 text-[10px] text-white/38">
|
|
||||||
{new Date(session.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5 grid grid-cols-3 gap-2 text-[10px] text-white/42">
|
|
||||||
<div className="rounded-[8px] border border-[#8cb478]/14 bg-black/22 p-2">
|
|
||||||
<div>图片</div>
|
|
||||||
<b className="mt-1 block text-[13px] text-white">{session.images.length}</b>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-[8px] border border-[#8cb478]/14 bg-black/22 p-2">
|
|
||||||
<div>选中</div>
|
|
||||||
<b className="mt-1 block text-[13px] text-[#e6f578]">{selectedCount}</b>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-[8px] border border-[#8cb478]/14 bg-black/22 p-2">
|
|
||||||
<div>素材包</div>
|
|
||||||
<b className="mt-1 block text-[13px] text-white">{packCount}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5">
|
|
||||||
<div className="mb-2 text-[10px] uppercase tracking-[0.14em] text-white/38">Reference</div>
|
|
||||||
{refs.length ? (
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
{refs.slice(0, 6).map((ref, index) => (
|
|
||||||
<div key={`${ref.url}-${index}`} className="relative aspect-square overflow-hidden rounded-[8px] bg-white ring-1 ring-white/10">
|
|
||||||
<img src={ref.url} alt={ref.label} className="h-full w-full object-contain" />
|
|
||||||
<span className="absolute bottom-1 left-1 rounded bg-black/70 px-1.5 py-0.5 text-[9px] text-white/70">
|
|
||||||
{ref.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="rounded-[8px] border border-dashed border-[#8cb478]/18 bg-black/18 px-3 py-4 text-xs text-white/34">
|
|
||||||
没有参考图
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5 rounded-[8px] border border-[#8cb478]/14 bg-black/22 p-3">
|
|
||||||
<div className="text-[10px] uppercase tracking-[0.14em] text-white/38">Session ID</div>
|
|
||||||
<div className="mt-2 break-all font-mono text-[10px] text-white/42">{session.id}</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [sessions, setSessions] = useState<GenSession[]>([]);
|
const [sessions, setSessions] = useState<GenSession[]>([]);
|
||||||
const [current, setCurrent] = useState<GenSession | null>(null);
|
const [current, setCurrent] = useState<GenSession | null>(null);
|
||||||
@@ -439,35 +353,30 @@ export default function Home() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{current && (
|
{current && (
|
||||||
<section className="card overflow-hidden">
|
<section className="space-y-5">
|
||||||
<div className="grid lg:grid-cols-[268px_minmax(0,1fr)]">
|
<div className="flex items-end justify-between gap-4">
|
||||||
<CurrentSessionRail session={current} />
|
<div>
|
||||||
<div className="space-y-5 p-7">
|
<span className="section-eyebrow">Step · 02 · Quick Screen</span>
|
||||||
<div className="flex items-end justify-between gap-4">
|
<h2 className="mt-2 text-lg font-semibold text-white">本次生成</h2>
|
||||||
<div>
|
<p className="text-xs text-white/40 mt-1">
|
||||||
<span className="section-eyebrow">Step · 03 · Quick Screen</span>
|
{new Date(current.createdAt).toLocaleString('zh-CN')}
|
||||||
<h2 className="mt-2 text-lg font-semibold text-white">本次生成</h2>
|
</p>
|
||||||
<p className="text-xs text-white/40 mt-1">
|
|
||||||
{new Date(current.createdAt).toLocaleString('zh-CN')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<code className="max-w-[220px] truncate text-[11px] text-white/30 font-mono">{current.id}</code>
|
|
||||||
</div>
|
|
||||||
<ResultGrid images={current.images} onAction={handleAction} />
|
|
||||||
<PackPanel
|
|
||||||
session={current}
|
|
||||||
loadingKind={loadingKind}
|
|
||||||
allLoading={allLoading}
|
|
||||||
characterLoading={characterLoading}
|
|
||||||
videoLoading={videoLoading}
|
|
||||||
onGenerate={handleGeneratePack}
|
|
||||||
onGenerateAll={handleGenerateAll}
|
|
||||||
onLockCharacter={handleLockCharacter}
|
|
||||||
onRegenerateAsset={handleRegenerateAsset}
|
|
||||||
onGenerateVideo={handleGenerateVideo}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<code className="max-w-[220px] truncate text-[11px] text-white/30 font-mono">{current.id}</code>
|
||||||
</div>
|
</div>
|
||||||
|
<ResultGrid images={current.images} onAction={handleAction} />
|
||||||
|
<PackPanel
|
||||||
|
session={current}
|
||||||
|
loadingKind={loadingKind}
|
||||||
|
allLoading={allLoading}
|
||||||
|
characterLoading={characterLoading}
|
||||||
|
videoLoading={videoLoading}
|
||||||
|
onGenerate={handleGeneratePack}
|
||||||
|
onGenerateAll={handleGenerateAll}
|
||||||
|
onLockCharacter={handleLockCharacter}
|
||||||
|
onRegenerateAsset={handleRegenerateAsset}
|
||||||
|
onGenerateVideo={handleGenerateVideo}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,76 @@
|
|||||||
|
|
||||||
import type { GenSession } from '@/lib/types';
|
import type { GenSession } from '@/lib/types';
|
||||||
|
|
||||||
|
function modeLabel(mode?: GenSession['inputMode']) {
|
||||||
|
if (mode === 'remix') return '二创';
|
||||||
|
if (mode === 'replicate') return '复刻';
|
||||||
|
if (mode === 'extend') return '补全';
|
||||||
|
return '想法';
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageSourcesForSession(session: GenSession) {
|
||||||
|
const uploaded = session.uploadedImages?.map(image => ({
|
||||||
|
url: image.url,
|
||||||
|
label: image.role === 'subject' ? '主体图' : image.role === 'reference' ? '参考图' : image.accessoryName || image.role,
|
||||||
|
})) ?? [];
|
||||||
|
if (uploaded.length) return uploaded;
|
||||||
|
return session.refImages.map((url, index) => ({ url, label: `参考 ${index + 1}` }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ActiveSessionDetail({ session }: { session: GenSession }) {
|
||||||
|
const refs = imageSourcesForSession(session);
|
||||||
|
const selectedCount = session.images.filter(image => image.status === 'selected').length;
|
||||||
|
const packCount = session.packs?.length ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ml-3 mt-2 rounded-[8px] border border-[#e6f578]/24 bg-[#e6f578]/[0.055] p-3 shadow-[0_18px_52px_-38px_rgba(230,245,120,0.82)]">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div className="text-[9px] uppercase tracking-[0.16em] text-[#e6f578]/65">下级信息</div>
|
||||||
|
<span className="rounded-full border border-white/10 bg-black/28 px-2 py-0.5 text-[9px] text-white/48">
|
||||||
|
{modeLabel(session.inputMode)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 line-clamp-4 text-[11px] font-medium leading-relaxed text-white/80">
|
||||||
|
{session.characterSpec?.name || session.prompt || '未填写'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 grid grid-cols-3 gap-1.5 text-[9px] text-white/44">
|
||||||
|
<div className="rounded-[6px] bg-black/22 px-2 py-1.5">
|
||||||
|
<div>图片</div>
|
||||||
|
<b className="mt-0.5 block text-[11px] text-white/78">{session.images.length}</b>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-[6px] bg-black/22 px-2 py-1.5">
|
||||||
|
<div>选中</div>
|
||||||
|
<b className="mt-0.5 block text-[11px] text-[#e6f578]">{selectedCount}</b>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-[6px] bg-black/22 px-2 py-1.5">
|
||||||
|
<div>包</div>
|
||||||
|
<b className="mt-0.5 block text-[11px] text-white/78">{packCount}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{refs.length > 0 && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<div className="mb-1.5 text-[9px] uppercase tracking-[0.14em] text-white/34">Reference</div>
|
||||||
|
<div className="grid grid-cols-4 gap-1.5">
|
||||||
|
{refs.slice(0, 4).map((ref, index) => (
|
||||||
|
<div key={`${ref.url}-${index}`} className="relative aspect-square overflow-hidden rounded-[6px] bg-white ring-1 ring-white/10">
|
||||||
|
<img src={ref.url} alt={ref.label} className="h-full w-full object-contain" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-3 flex items-center justify-between gap-2 text-[9px] text-white/36">
|
||||||
|
<span>{new Date(session.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</span>
|
||||||
|
<span className="font-mono">{session.id.slice(0, 10)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
open,
|
open,
|
||||||
onToggle,
|
onToggle,
|
||||||
@@ -109,6 +179,7 @@ export default function Sidebar({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
{active && <ActiveSessionDetail session={s} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user