From 4551eee9d10532cc077dea0114e28bcda0d972b3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 18 Apr 2026 12:44:35 +0800 Subject: [PATCH] Match LibTV character preset canvas --- .../canvas/LiblibVideoCanvasReplica.tsx | 774 ++++++++++++++++-- 1 file changed, 687 insertions(+), 87 deletions(-) diff --git a/components/canvas/LiblibVideoCanvasReplica.tsx b/components/canvas/LiblibVideoCanvasReplica.tsx index d4fa69de9..9a9a5c9d9 100644 --- a/components/canvas/LiblibVideoCanvasReplica.tsx +++ b/components/canvas/LiblibVideoCanvasReplica.tsx @@ -2,7 +2,6 @@ import { Bell, - Box, ChevronDown, Clock3, Copy, @@ -14,7 +13,6 @@ import { Image as ImageIcon, Layers, Loader2, - MousePointer2, Music2, Plus, Save, @@ -24,7 +22,6 @@ import { UserRound, Video, X, - Zap, type LucideIcon, } from "lucide-react"; import { useRouter } from "next/navigation"; @@ -56,6 +53,22 @@ type ActivePanel = | "account" | null; +type ActiveStarterScene = "character-three-view" | null; +type SceneToolbarMenu = "panorama" | "orbit" | "lighting" | "grid" | "hd" | "crop" | null; +type ComposerMenu = + | "style" + | "mark" + | "focus" + | "model" + | "ratio" + | "resolution" + | "camera" + | "panorama" + | "translate" + | "settings" + | "count" + | null; + type CapturedTool = { type?: string; toolKey?: string; @@ -154,25 +167,30 @@ const STARTER_CARDS = [ { title: "故事脚本生成", subtitle: "脚本到分镜", - coverIndex: 0, + coverUrl: + "https://liblibai-online.liblib.cloud/web/ef0097ae9da74189a0f5d604e98c77cd/f9d23335-4ec2-412d-9954-91c58f4b5eb3.png", steps: ["故事脚本", "多模态文本模型Pro", "可灵 3.0", "预览"], }, { title: "角色三视图", subtitle: "角色资产沉淀", - coverIndex: 4, + coverUrl: + "https://liblibai-online.liblib.cloud/web/ef0097ae9da74189a0f5d604e98c77cd/e3731aeb-a95b-46e9-99e7-3cd7f22a1e3f.png", steps: ["角色描述", "角色三视图", "全能图片模型V2", "保存"], }, { title: "首帧图生视频", subtitle: "参考图到成片", - coverIndex: 10, + coverUrl: + "https://liblibai-online.liblib.cloud/web/ef0097ae9da74189a0f5d604e98c77cd/f407cb9c-8dd7-436b-8e80-88da1cebf25d.png", steps: ["上传图片", "首帧图", "可灵 3.0", "预览"], }, { title: "音频生视频", subtitle: "音频驱动画面", - coverIndex: 17, + coverUrl: + "https://liblibai-online.liblib.cloud/web/ef0097ae9da74189a0f5d604e98c77cd/bd88df14-04bd-473d-9016-ec72a5273bfb.png", + icon: "music", steps: ["上传音频", "音频主视频", "Wan 2.6", "预览"], }, ]; @@ -188,6 +206,19 @@ const TOOLBAR_ITEMS: Array<{ id: Exclude; label: string; icon const DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 }; +const TARGET_AVATAR = + "https://liblibai-online.liblib.cloud/web/1656e2f1b3c74f1ba823b610f1b2cbdf/0c3eb5e2-1dc0-405c-a823-07b2570237f8.jpg?x-oss-process=image/resize,w_100,m_lfit/format,webp"; +const CHARACTER_IMAGE_URL = + "https://liblibai-online.liblib.cloud/web/ea95cb51f3d94addbb92dc003cd33b49/cca9e085-dccc-4f72-aef7-d68dc6688bcf.png"; +const CHARACTER_THREE_VIEW_URL = + "https://liblibai-online.liblib.cloud/web/ea95cb51f3d94addbb92dc003cd33b49/7a32ba0b-7b5b-46b1-a86c-e0904482adf7.png"; + +function normalizeCanvasTitle(title?: string | null) { + const value = String(title || "").trim(); + if (!value || value === "未命名视频画布") return "未命名"; + return value; +} + function getToolType(tool?: CapturedTool) { return String(tool?.type || "video").trim() || "video"; } @@ -434,6 +465,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib const [draftTitle, setDraftTitle] = useState("未命名"); const [isTitleEditing, setIsTitleEditing] = useState(false); const [activePanel, setActivePanel] = useState(null); + const [activeStarterScene, setActiveStarterScene] = useState(null); const [toolTab, setToolTab] = useState<(typeof PANEL_TABS)[number]>("全部"); const [toolSearch, setToolSearch] = useState(""); const [isProjectLoading, setIsProjectLoading] = useState(true); @@ -475,8 +507,9 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib const result = await canvasService.getProject(canvasId); if (!active) return; if (result.success && result.data) { - setProjectTitle(result.data.title || "未命名"); - setDraftTitle(result.data.title || "未命名"); + const title = normalizeCanvasTitle(result.data.title); + setProjectTitle(title); + setDraftTitle(title); const restoredNodes = restoreNodes(result.data.canvasData); const restoredEdges = restoreEdges(result.data.canvasData); setNodes(restoredNodes); @@ -577,6 +610,16 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib const createStarter = useCallback( (starter: (typeof STARTER_CARDS)[number]) => { + if (starter.title === "角色三视图") { + setNodes([]); + setEdges([]); + setSelectedNodeId(null); + setActivePanel(null); + setActiveStarterScene("character-three-view"); + return; + } + + setActiveStarterScene(null); const baseX = 380; const baseY = 325; const newNodes = starter.steps.map((title, index) => { @@ -686,14 +729,13 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib return (
-
-
+
- {nodes.length === 0 && !activePanel ? ( + {activeStarterScene === "character-three-view" ? : null} + + {nodes.length === 0 && !activePanel && !activeStarterScene ? (
-
- - 双击画布 自由生成节点 +
+ + 双击画布 + 自由生成节点
{STARTER_CARDS.map((card) => { - const cover = capturedWorkflows[card.coverIndex]?.cover || capturedWorkflows[0]?.cover || ""; + const cover = card.coverUrl || capturedWorkflows[0]?.cover || ""; return ( ); })} @@ -742,26 +794,31 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
) : null} - setActivePanel("account")} - onBell={() => setActivePanel("notifications")} - onCommitTitle={commitTitle} - onDraftTitleChange={setDraftTitle} - onMember={() => setActivePanel("member")} - onSave={saveCanvas} - onShare={() => setActivePanel("share")} - onSkills={() => setActivePanel("skills")} - onStartTitleEdit={() => setIsTitleEditing(true)} - projectTitle={projectTitle} - routerBack={() => router.push("/")} - /> + {!activeStarterScene ? ( + <> + setActivePanel("account")} + onBell={() => setActivePanel("notifications")} + onCommitTitle={commitTitle} + onDraftTitleChange={setDraftTitle} + onMember={() => setActivePanel("member")} + onSave={saveCanvas} + onShare={() => setActivePanel("share")} + onSkills={() => setActivePanel("skills")} + onStartTitleEdit={() => setIsTitleEditing(true)} + projectTitle={projectTitle} + routerBack={() => router.push("/")} + /> - setActivePanel((current) => current === panel ? null : panel)} /> + setActivePanel((current) => current === panel ? null : panel)} /> + + ) : null} setViewport(DEFAULT_VIEWPORT)} onZoomIn={() => zoomBy(0.1)} @@ -969,19 +1026,20 @@ function TopBar({ routerBack: () => void; }) { return ( -
-
- -
+
{isTitleEditing ? ( onDraftTitleChange(event.target.value)} onKeyDown={(event) => { @@ -990,7 +1048,11 @@ function TopBar({ value={draftTitle} /> ) : ( - )} @@ -1004,20 +1066,15 @@ function TopBar({ - + + - - + + 会员超市 - - - 会员特惠 - - - - +
); @@ -1037,7 +1094,7 @@ function TopButton({ return ( +
+ + {index === 3 ?
: null} +
); })} @@ -1084,44 +1149,579 @@ function LeftToolbar({ } function ZoomDock({ + compact = false, onCenter, onZoomIn, onZoomOut, zoom, }: { + compact?: boolean; onCenter: () => void; onZoomIn: () => void; onZoomOut: () => void; zoom: number; }) { + if (compact) { + return ( +
+ + + + +
+ ); + } + return (
- - - -
- - -
); } +function LibTVLogo() { + return ( + + ); +} + +function PointerBadge() { + return ( + + ); +} + +function ArrowGlyph() { + return ( + + ); +} + +function MusicStarterIcon() { + return ( + + + + ); +} + +function StoreGlyph() { + return ( + + ); +} + +function DiamondGlyph() { + return ( + + ); +} + +function LightningGlyph() { + return ( + + ); +} + +function MemberDealPill({ onAccount, onMember }: { onAccount: () => void; onMember: () => void }) { + return ( +
+ + + +
+ ); +} + +function AssetGlyph() { + return ( + + ); +} + +function WorkflowGlyph() { + return ( + + ); +} + +function GridTilesGlyph() { + return ( + + ); +} + +function MapPinGlyph() { + return ( + + ); +} + +function MagnetGlyph() { + return ( + + ); +} + +function CharacterThreeViewScene() { + const [toolbarMenu, setToolbarMenu] = useState(null); + + return ( +
+
+ setToolbarMenu((current) => current === "panorama" ? null : "panorama")} /> + setToolbarMenu((current) => current === "orbit" ? null : "orbit")} /> + setToolbarMenu((current) => current === "lighting" ? null : "lighting")} /> + setToolbarMenu((current) => current === "grid" ? null : "grid")} /> + setToolbarMenu((current) => current === "hd" ? null : "hd")} /> + setToolbarMenu((current) => current === "crop" ? null : "crop")} /> +
+ {["⌁", "↔", "↓", "↗"].map((item, index) => ( + + ))} +
+ {toolbarMenu ? setToolbarMenu(null)} /> : null} + +
+ +
+
+ +
+ +
+ + +
+ ); +} + +function SceneTool({ + active, + badge, + dropdown, + kind, + label, + onClick, +}: { + active?: boolean; + badge?: string; + dropdown?: boolean; + kind: "panorama" | "orbit" | "lighting" | "grid" | "hd" | "crop"; + label: string; + onClick: () => void; +}) { + return ( + + ); +} + +function SceneToolbarPopover({ + menu, + onClose, +}: { + menu: Exclude; + onClose: () => void; +}) { + const quickOptions: Record, string[]> = { + crop: ["4 宫格切分", "9 宫格切分", "16 宫格切分", "25 宫格切分"], + grid: ["2 x 2", "3 x 3", "4 x 4", "自定义宫格"], + hd: ["2倍高清", "4倍高清"], + orbit: ["正侧背三视角", "八方向参考", "环绕镜头"], + panorama: ["全景", "半身", "近景"], + }; + + if (menu === "lighting") { + return ( +
+
+
打光
+ +
+