From 794d95e7d5fb9b57f1996d49025e12206c576aa4 Mon Sep 17 00:00:00 2001 From: kang Date: Wed, 20 May 2026 15:01:21 +0800 Subject: [PATCH] fix: redesign project workbench layout --- .memory/worklog.json | 21 ++++ src/app/globals.css | 235 +++++++++++++++++++++++++++++++++++ src/app/page.tsx | 234 +++++++++++++++++++++++++---------- src/components/Sidebar.tsx | 244 +++++++++++++++---------------------- 4 files changed, 525 insertions(+), 209 deletions(-) diff --git a/.memory/worklog.json b/.memory/worklog.json index 75186e2..d1a6c3f 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1524,6 +1524,27 @@ "message": "auto-save 2026-05-20 12:49 (~3)", "hash": "a190800", "files_changed": 3 + }, + { + "ts": "2026-05-20T14:07:08+08:00", + "type": "commit", + "message": "auto-save 2026-05-20 14:07 (-1)", + "hash": "09664a2", + "files_changed": 1 + }, + { + "ts": "2026-05-20T14:12:33+08:00", + "type": "commit", + "message": "auto-save 2026-05-20 14:12 (+1, ~3)", + "hash": "6bd8873", + "files_changed": 4 + }, + { + "ts": "2026-05-20T14:45:05+08:00", + "type": "commit", + "message": "auto-save 2026-05-20 14:45 (~4)", + "hash": "527ccfa", + "files_changed": 4 } ] } diff --git a/src/app/globals.css b/src/app/globals.css index 54cd7ec..015d50e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -342,6 +342,207 @@ input, textarea { background: linear-gradient(90deg, rgba(0,0,0,0.16), rgba(42,42,42,0.08)); } +.project-shell { + background: + radial-gradient(circle at 22% 8%, rgba(230, 245, 120, 0.12), transparent 34%), + radial-gradient(circle at 86% 18%, rgba(73, 199, 182, 0.09), transparent 30%), + linear-gradient(135deg, rgba(255,255,255,0.025), rgba(255,255,255,0)); +} + +.project-dock { + border-right: 1px solid rgba(255, 255, 255, 0.10); + background: + linear-gradient(180deg, rgba(36, 36, 36, 0.76), rgba(12, 12, 12, 0.64)), + rgba(24, 24, 24, 0.60); + box-shadow: + inset -1px 0 0 rgba(255,255,255,0.04), + 18px 0 70px -48px rgba(0,0,0,0.96); + backdrop-filter: blur(20px); +} + +.project-dock--closed { + display: flex; + justify-content: center; +} + +.project-dock-list, +.project-brief-panel, +.project-production-panel { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.18) transparent; +} + +.project-dock-list::-webkit-scrollbar, +.project-brief-panel::-webkit-scrollbar, +.project-production-panel::-webkit-scrollbar { + width: 8px; +} + +.project-dock-list::-webkit-scrollbar-thumb, +.project-brief-panel::-webkit-scrollbar-thumb, +.project-production-panel::-webkit-scrollbar-thumb { + border-radius: 999px; + background: rgba(255, 255, 255, 0.16); +} + +.project-stage { + overflow: hidden; +} + +.project-stage-inner { + display: flex; + height: 100%; + min-height: 0; + flex-direction: column; + gap: 14px; + padding: 18px 22px 20px; +} + +.project-topbar { + min-height: 58px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + border: 1px solid rgba(255,255,255,0.09); + border-radius: 8px; + background: + linear-gradient(135deg, rgba(255,255,255,0.065), rgba(255,255,255,0.026)), + rgba(24,24,24,0.46); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.07); + backdrop-filter: blur(18px); + padding: 10px 14px; +} + +.project-empty-canvas { + min-height: 0; + overflow: auto; +} + +.project-board { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + gap: 14px; +} + +.project-board-head { + min-height: 102px; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(300px, 560px); + gap: 18px; + align-items: stretch; + border: 1px solid rgba(255,255,255,0.10); + border-radius: 8px; + background: + linear-gradient(135deg, rgba(255,255,255,0.07), rgba(255,255,255,0.026)), + rgba(28,28,28,0.50); + box-shadow: + inset 0 1px 0 rgba(255,255,255,0.08), + 0 24px 90px -54px rgba(0, 0, 0, 0.92); + backdrop-filter: blur(18px); + padding: 16px; +} + +.project-reference-strip { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 8px; + min-width: 0; +} + +.project-reference-tile { + position: relative; + overflow: hidden; + border-radius: 8px; + background: rgba(255,255,255,0.92); + min-height: 70px; + aspect-ratio: 1 / 1; + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.32); +} + +.project-reference-tile span { + position: absolute; + left: 6px; + right: 6px; + bottom: 5px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 6px; + background: rgba(0,0,0,0.54); + padding: 2px 5px; + color: rgba(255,255,255,0.76); + font-size: 9px; +} + +.project-board-grid { + display: grid; + grid-template-columns: minmax(250px, 310px) minmax(0, 1fr) 88px; + gap: 14px; + min-height: 0; + flex: 1; + align-items: stretch; +} + +.project-brief-panel { + min-height: 0; + overflow-y: auto; + border: 1px solid rgba(255,255,255,0.10); + border-radius: 8px; + background: + linear-gradient(160deg, rgba(255,255,255,0.07), rgba(255,255,255,0.025)), + rgba(24,24,24,0.52); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.07); + backdrop-filter: blur(18px); + padding: 16px; +} + +.project-primary-preview { + position: relative; + display: grid; + place-items: center; + overflow: hidden; + border-radius: 8px; + background: rgba(255,255,255,0.92); + aspect-ratio: 4 / 3; + min-height: 190px; + box-shadow: + 0 20px 60px -38px rgba(230,245,120,0.7), + inset 0 0 0 1px rgba(255,255,255,0.36); +} + +.project-stat { + border-radius: 8px; + background: rgba(255,255,255,0.045); + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07); + padding: 10px; + color: rgba(255,255,255,0.82); +} + +.project-stat--accent { + background: rgba(230,245,120,0.13); + color: #e6f578; + box-shadow: inset 0 0 0 1px rgba(230,245,120,0.22); +} + +.project-spec-card { + border-radius: 8px; + background: rgba(0,0,0,0.22); + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07); + padding: 13px; +} + +.project-production-panel { + min-height: 0; + overflow: hidden; +} + +.project-production-panel > div { + min-height: 0; +} + .dashboard-workbench { border: 1px solid rgba(255, 255, 255, 0.10); border-radius: 8px; @@ -439,9 +640,43 @@ input, textarea { grid-template-columns: minmax(0, 1fr) 84px; gap: 22px; } + + .project-board-grid { + grid-template-columns: minmax(280px, 340px) minmax(0, 1fr) 96px; + gap: 18px; + } } @media (max-width: 1180px) { + .project-shell { + overflow: auto; + } + + .project-stage { + overflow: visible; + } + + .project-stage-inner { + height: auto; + min-height: 100vh; + padding: 14px; + } + + .project-board-head, + .project-board-grid { + display: flex; + flex-direction: column; + } + + .project-reference-strip { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .project-brief-panel, + .project-production-panel { + overflow: visible; + } + .session-workspace { height: auto; min-height: 0; diff --git a/src/app/page.tsx b/src/app/page.tsx index 9273e3c..07816ca 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import Sidebar from '@/components/Sidebar'; import PackPanel from '@/components/PackPanel'; import ProjectGalleryDrawer from '@/components/ProjectGalleryDrawer'; import { OasisCanvas } from '@/components/login/OasisCanvas'; +import { PACK_ORDER, PACK_TEMPLATES } from '@/lib/templates'; import type { GenImage, GenSession, @@ -22,6 +23,124 @@ import type { } from '@/lib/types'; import type { VIDEO_TEMPLATES } from '@/lib/templates'; +function modeLabel(mode?: GenSession['inputMode']) { + if (mode === 'remix') return '二创项目'; + if (mode === 'replicate') return '复刻项目'; + if (mode === 'extend') return '补全项目'; + return '想法项目'; +} + +function projectTitle(session: GenSession) { + return session.characterSpec?.name || session.prompt || '未命名项目'; +} + +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 packSlotTotal() { + return PACK_ORDER.reduce((sum, kind) => sum + PACK_TEMPLATES[kind].length, 0); +} + +function ProjectStat({ label, value, tone }: { label: string; value: string | number; tone?: 'accent' | 'soft' }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function ReferenceStrip({ session }: { session: GenSession }) { + const refs = imageSourcesForSession(session); + if (!refs.length) return null; + + return ( +
+ {refs.slice(0, 6).map((ref, index) => ( +
+ {ref.label} + {ref.label} +
+ ))} +
+ ); +} + +function ProjectBrief({ session }: { session: GenSession }) { + const selectedImages = session.images.filter(image => image.status === 'selected'); + const primaryImage = selectedImages[0] ?? session.images[0] ?? null; + const generatedAssets = (session.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0); + const totalSlots = packSlotTotal(); + + return ( +
+
+
+ Project Core +

{projectTitle(session)}

+

+ {session.characterSpec?.oneLiner || session.prompt || '当前项目暂无描述'} +

+
+ + {modeLabel(session.inputMode)} + +
+ + {primaryImage && ( +
+ 当前主方案 +
+ Primary + {primaryImage.status === 'selected' ? '已选中' : '待筛选'} +
+
+ )} + +
+ + + +
+ + {session.characterSpec ? ( +
+
+ 角色设定 + 已锁定 +
+
+ {[ + ['形态', session.characterSpec.speciesShape], + ['比例', session.characterSpec.bodyRatio], + ['配色', session.characterSpec.colorPalette.join('、')], + ['材料', session.characterSpec.materials.join('、')], + ].map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+
+ ) : ( +
+
角色设定
+

+ 从右侧图库选中主方案后,在生产矩阵里锁定角色设定。 +

+
+ )} +
+ ); +} + export default function Home() { const [sessions, setSessions] = useState([]); const [current, setCurrent] = useState(null); @@ -282,35 +401,24 @@ export default function Home() {
-
- setSidebarOpen(v => !v)} - sessions={sessions} - currentId={current?.id ?? null} - onPick={id => setCurrent(sessions.find(s => s.id === id) ?? null)} - onNew={() => setCurrent(null)} - /> -
-
-
-
-
- - - -
+
+ setSidebarOpen(v => !v)} + sessions={sessions} + currentId={current?.id ?? null} + onPick={id => setCurrent(sessions.find(s => s.id === id) ?? null)} + onNew={() => setCurrent(null)} + /> +
+
+
-

- AI Toy Patent - 把一句想法变成专利 / 生产 / 宣发素材包 -

-

批量出意向 · 快筛 · 锁定角色 · 一键四包 · Seedance 视频

+ AI Toy Patent +

项目生产工作台

-
-
- {current && ( - <> +
+ {current && ( 记录 - - )} - - - - {provider === 'gpt' ? 'GPT · gpt-image-2' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider} - -
-
+ )} + + + + {provider === 'gpt' ? 'GPT · gpt-image-2' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider} + +
+ -
{!current && ( - +
+ +
)} + {current && ( -
-
-
- Project Workspace -

项目工作台

-

- {new Date(current.createdAt).toLocaleString('zh-CN')} +

+
+
+ Current Project +

{projectTitle(current)}

+

+ {new Date(current.createdAt).toLocaleString('zh-CN')} · {current.id}

- {current.id} +
-
- +
)}
-
- +
); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d1facf6..3de7b75 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -9,66 +9,65 @@ function modeLabel(mode?: GenSession['inputMode']) { 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 projectTitle(session: GenSession) { + return session.characterSpec?.name || session.prompt || '未命名项目'; } -function ActiveSessionDetail({ session }: { session: GenSession }) { - const refs = imageSourcesForSession(session); +function ProjectThumbs({ session }: { session: GenSession }) { + const refs = session.uploadedImages?.map(image => image.url) ?? session.refImages; + const thumbs = refs.length ? refs : session.images.map(image => image.url); + + return ( +
+ {thumbs.slice(0, 3).map((url, index) => ( + + + + ))} + {thumbs.length === 0 && ( + + - + + )} +
+ ); +} + +function ProjectCard({ session, active, onPick }: { session: GenSession; active: boolean; onPick: () => void }) { const selectedCount = session.images.filter(image => image.status === 'selected').length; const packCount = session.packs?.length ?? 0; return ( -
-
-
Current session
- - {modeLabel(session.inputMode)} - -
- -
- {session.characterSpec?.name || session.prompt || '未填写'} -
- -
-
-
图片
- {session.images.length} -
-
-
选中
- {selectedCount} -
-
-
- {packCount} -
-
- - {refs.length > 0 && ( -
-
Reference
-
- {refs.slice(0, 4).map((ref, index) => ( -
- {ref.label} -
- ))} + ); } @@ -87,15 +86,14 @@ export default function Sidebar({ onPick: (id: string) => void; onNew: () => void; }) { - const activeSession = sessions.find(session => session.id === currentId) ?? null; - if (!open) { return ( -
-
-
-
-
Toy Patent
-
AI 玩具工作流
-
- -
+ -
- -
+
+ Projects + {sessions.length} +
- {activeSession && ( -
- +
+ {sessions.length === 0 && ( +
+ 还没有项目
)} + {sessions.map(session => ( + onPick(session.id)} + /> + ))} +
-
- 最近会话 -
- -
- {sessions.length === 0 && ( -
- 还没有生成记录 -
- )} - {sessions.map(s => { - const selectedCount = s.images.filter(i => i.status === 'selected').length; - const active = currentId === s.id; - return ( -
- -
- ); - })} -
- -
- 本地端口 4560 · 数据 / data -
+
+ 本地 Docker · 4560