fix: move selected session detail to sidebar
This commit is contained in:
@@ -343,13 +343,15 @@ export default function Home() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<PromptPanel
|
{!current && (
|
||||||
session={current}
|
<PromptPanel
|
||||||
onGenerate={handleGenerate}
|
session={null}
|
||||||
onUploadProject={handleUploadProject}
|
onGenerate={handleGenerate}
|
||||||
loading={loading}
|
onUploadProject={handleUploadProject}
|
||||||
uploadLoading={uploadLoading}
|
loading={loading}
|
||||||
/>
|
uploadLoading={uploadLoading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{current && (
|
{current && (
|
||||||
<section className="space-y-5">
|
<section className="space-y-5">
|
||||||
<div className="flex items-end justify-between">
|
<div className="flex items-end justify-between">
|
||||||
|
|||||||
@@ -2,6 +2,72 @@
|
|||||||
|
|
||||||
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="rounded-[8px] border border-[#e6f578]/18 bg-black/28 p-4 shadow-[0_18px_50px_-34px_rgba(230,245,120,0.72)]">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-[9px] uppercase tracking-[0.18em] text-[#e6f578]/60">Session</span>
|
||||||
|
<span className="rounded-full border border-white/10 bg-white/[0.04] px-2 py-0.5 text-[9px] text-white/42">
|
||||||
|
{modeLabel(session.inputMode)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 line-clamp-4 text-[12px] font-medium leading-relaxed text-white/82">
|
||||||
|
{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-white/[0.045] 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-white/[0.045] 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-white/[0.045] 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/32">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,
|
||||||
@@ -33,88 +99,108 @@ export default function Sidebar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeSession = sessions.find(session => session.id === currentId) ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="glass-sidebar w-72 shrink-0 flex flex-col">
|
<aside className="flex shrink-0 items-stretch">
|
||||||
<div className="px-4 pt-5 pb-3 flex items-center gap-3">
|
<div className="glass-sidebar w-72 shrink-0 flex flex-col">
|
||||||
<div className="w-8 h-8 rounded-[8px] bg-gradient-to-br from-[#e6f578] to-[#d6b36a] flex items-center justify-center shadow-glow-violet">
|
<div className="px-4 pt-5 pb-3 flex items-center gap-3">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#081006" strokeWidth="2.5">
|
<div className="w-8 h-8 rounded-[8px] bg-gradient-to-br from-[#e6f578] to-[#d6b36a] flex items-center justify-center shadow-glow-violet">
|
||||||
<path d="M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" strokeLinejoin="round" />
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#081006" strokeWidth="2.5">
|
||||||
</svg>
|
<path d="M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" strokeLinejoin="round" />
|
||||||
</div>
|
</svg>
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="text-[13px] font-semibold tracking-tight">Toy Patent</div>
|
|
||||||
<div className="text-[10px] text-white/40">AI 玩具工作流</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={onToggle}
|
|
||||||
className="w-8 h-8 rounded-lg hover:bg-white/[0.08] flex items-center justify-center text-white/60 transition-colors"
|
|
||||||
title="收起侧栏"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M15 18l-6-6 6-6" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-4 pb-4">
|
|
||||||
<button
|
|
||||||
onClick={onNew}
|
|
||||||
className="btn btn-primary w-full text-sm"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
||||||
<path d="M12 5v14M5 12h14" strokeLinecap="round" />
|
|
||||||
</svg>
|
|
||||||
新会话 · 重新选择
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-5 pt-1 pb-2 text-[10px] font-semibold text-white/35 uppercase tracking-[0.18em]">
|
|
||||||
最近会话
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto px-3 pb-4 space-y-1">
|
|
||||||
{sessions.length === 0 && (
|
|
||||||
<div className="px-3 py-12 text-center text-xs text-white/30">
|
|
||||||
还没有生成记录
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex-1 min-w-0">
|
||||||
{sessions.map(s => {
|
<div className="text-[13px] font-semibold tracking-tight">Toy Patent</div>
|
||||||
const selectedCount = s.images.filter(i => i.status === 'selected').length;
|
<div className="text-[10px] text-white/40">AI 玩具工作流</div>
|
||||||
const active = currentId === s.id;
|
</div>
|
||||||
return (
|
<button
|
||||||
<button
|
onClick={onToggle}
|
||||||
key={s.id}
|
className="w-8 h-8 rounded-lg hover:bg-white/[0.08] flex items-center justify-center text-white/60 transition-colors"
|
||||||
onClick={() => onPick(s.id)}
|
title="收起侧栏"
|
||||||
className={`w-full text-left px-3 py-2.5 rounded-[8px] text-xs transition-all ${
|
>
|
||||||
active
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
? 'bg-gradient-to-r from-[#e6f578]/24 via-[#8cb478]/18 to-[#d6b36a]/16 ring-1 ring-[#e6f578]/35 text-white shadow-glow-violet'
|
<path d="M15 18l-6-6 6-6" />
|
||||||
: 'hover:bg-[#e6f578]/[0.06] text-white/75 ring-1 ring-transparent hover:ring-[#8cb478]/20'
|
</svg>
|
||||||
}`}
|
</button>
|
||||||
>
|
</div>
|
||||||
<div className={`line-clamp-2 font-medium leading-relaxed ${active ? 'text-white' : 'text-white/85'}`}>
|
|
||||||
{s.prompt}
|
<div className="px-4 pb-4">
|
||||||
</div>
|
<button
|
||||||
<div className={`mt-1.5 flex justify-between items-center text-[10px] ${active ? 'text-white/65' : 'text-white/40'}`}>
|
onClick={onNew}
|
||||||
<span>
|
className="btn btn-primary w-full text-sm"
|
||||||
{new Date(s.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
|
>
|
||||||
</span>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||||
<span className="flex items-center gap-1.5">
|
<path d="M12 5v14M5 12h14" strokeLinecap="round" />
|
||||||
<span>{s.images.length} 张</span>
|
</svg>
|
||||||
{selectedCount > 0 && (
|
新会话 · 重新选择
|
||||||
<span className={active ? 'text-[#e6f578] font-semibold' : 'text-[#d6b36a] font-semibold'}>
|
</button>
|
||||||
✓{selectedCount}
|
</div>
|
||||||
|
|
||||||
|
<div className="px-5 pt-1 pb-2 text-[10px] font-semibold text-white/35 uppercase tracking-[0.18em]">
|
||||||
|
最近会话
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto px-3 pb-4 space-y-1">
|
||||||
|
{sessions.length === 0 && (
|
||||||
|
<div className="px-3 py-12 text-center text-xs text-white/30">
|
||||||
|
还没有生成记录
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{sessions.map(s => {
|
||||||
|
const selectedCount = s.images.filter(i => i.status === 'selected').length;
|
||||||
|
const active = currentId === s.id;
|
||||||
|
return (
|
||||||
|
<div key={s.id} className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => onPick(s.id)}
|
||||||
|
className={`w-full text-left px-3 py-2.5 rounded-[8px] text-xs transition-all ${
|
||||||
|
active
|
||||||
|
? 'bg-gradient-to-r from-[#e6f578]/24 via-[#8cb478]/18 to-[#d6b36a]/16 ring-1 ring-[#e6f578]/35 text-white shadow-glow-violet'
|
||||||
|
: 'hover:bg-[#e6f578]/[0.06] text-white/75 ring-1 ring-transparent hover:ring-[#8cb478]/20'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={`line-clamp-2 font-medium leading-relaxed ${active ? 'text-white' : 'text-white/85'}`}>
|
||||||
|
{s.characterSpec?.name || s.prompt}
|
||||||
|
</div>
|
||||||
|
<div className={`mt-1.5 flex justify-between items-center text-[10px] ${active ? 'text-white/65' : 'text-white/40'}`}>
|
||||||
|
<span>
|
||||||
|
{new Date(s.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
<span className="flex items-center gap-1.5">
|
||||||
</span>
|
<span>{s.images.length} 张</span>
|
||||||
|
{selectedCount > 0 && (
|
||||||
|
<span className={active ? 'text-[#e6f578] font-semibold' : 'text-[#d6b36a] font-semibold'}>
|
||||||
|
✓{selectedCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-white/[0.06] px-5 py-3 text-[10px] text-white/30">
|
||||||
|
本地端口 4560 · 数据 / data
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-white/[0.06] px-5 py-3 text-[10px] text-white/30">
|
{activeSession && (
|
||||||
本地端口 4560 · 数据 / data
|
<div className="glass-sidebar w-72 shrink-0 border-l border-[#e6f578]/10">
|
||||||
</div>
|
<div className="h-full overflow-y-auto px-4 py-5">
|
||||||
|
<div className="mb-3 flex items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] font-semibold uppercase tracking-[0.18em] text-[#e6f578]/70">Detail</div>
|
||||||
|
<div className="mt-1 text-[12px] text-white/45">已选项目</div>
|
||||||
|
</div>
|
||||||
|
<span className="h-2 w-2 rounded-full bg-[#e6f578] shadow-[0_0_18px_rgba(230,245,120,0.75)]" />
|
||||||
|
</div>
|
||||||
|
<ActiveSessionDetail session={activeSession} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user