From 2276ab750eb99717cc9a504dcdc2193f0ec1454c Mon Sep 17 00:00:00 2001 From: zhangerheo Date: Thu, 10 Sep 2026 11:33:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas)=EF=BC=9A=E7=94=BB=E5=B8=83=20-=20?= =?UTF-8?q?=E7=9B=B8=E6=9C=BA=E6=8E=A7=E5=88=B6=EF=BC=88=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=91=84=E5=BD=B1=E6=9C=BA=EF=BC=89=20(#473)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(canvas): 重构相机控制UI,拆分独立组件并调整位置 将原图像设置弹窗中的相机控制功能抽离为独立组件,调整相机控制按钮到图像设置前的布局,简化原弹窗组件代码 * refactor(canvas-toolbar): 调整光影工具分组与渲染逻辑 将原视角分组中的光影工具拆分到独立lighting分组,更新类型定义与工具栏渲染逻辑,让工具分组更贴合功能场景 --------- Co-authored-by: bensharp --- .../canvas/canvas-image-settings-popover.tsx | 54 +++----------- .../canvas/canvas-image-toolbar-tools.tsx | 2 +- .../canvas/canvas-node-prompt-panel.tsx | 70 +++++++++++++++---- .../components/canvas/canvas-node-toolbar.tsx | 4 +- .../canvas/tool-registry/tool-definition.ts | 2 +- 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/web/src/components/canvas/canvas-image-settings-popover.tsx b/web/src/components/canvas/canvas-image-settings-popover.tsx index a4ce12c00..bc453a6a0 100644 --- a/web/src/components/canvas/canvas-image-settings-popover.tsx +++ b/web/src/components/canvas/canvas-image-settings-popover.tsx @@ -1,14 +1,11 @@ import { useEffect, useRef, useState, type RefObject } from "react"; import { createPortal } from "react-dom"; -import { Camera, Settings2 } from "lucide-react"; +import { Settings2 } from "lucide-react"; import { Button } from "antd"; import { ImageSettingsPanel, imageQualityLabel, imageSizeLabel } from "@/components/image-settings-panel"; -import { CanvasNodeCameraPanel } from "@/components/canvas/canvas-node-camera-dialog"; -import { AppModal } from "@/components/ui/product/app-modal/app-modal"; import { canvasThemes } from "@/lib/canvas-theme"; import { modelCapabilityConfigFor, normalizeImageValue } from "@/lib/model-capabilities"; -import type { CameraControlOptions } from "@/lib/canvas/camera-prompt-library"; import { useThemeStore } from "@/stores/use-theme-store"; import type { AiConfig } from "@/stores/use-config-store"; @@ -22,17 +19,14 @@ type CanvasImageSettingsPopoverProps = { placement?: "topLeft" | "top" | "topRight" | "bottomLeft" | "bottom" | "bottomRight"; autoAdjustOverflow?: boolean; showCount?: boolean; - cameraControl?: CameraControlOptions; - onCameraControlChange?: (options: CameraControlOptions) => void; }; -export function CanvasImageSettingsPopover({ config, onConfigChange, onOpenChange, buttonClassName, placement = "topLeft", showCount = true, cameraControl, onCameraControlChange }: CanvasImageSettingsPopoverProps) { +export function CanvasImageSettingsPopover({ config, onConfigChange, onOpenChange, buttonClassName, placement = "topLeft", showCount = true }: CanvasImageSettingsPopoverProps) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; const buttonRef = useRef(null); const panelRef = useRef(null); const [open, setOpen] = useState(false); const [buttonRect, setButtonRect] = useState(null); - const [cameraOpen, setCameraOpen] = useState(false); const profile = modelCapabilityConfigFor(config, config.model || config.imageModel).image!; const normalized = normalizeImageValue(profile, config); const summaryParts = [ @@ -73,48 +67,16 @@ export function CanvasImageSettingsPopover({ config, onConfigChange, onOpenChang const panel = open && buttonRect ? : null; - if (!hasSettings && !onCameraControlChange) return null; - - const cameraEnabled = cameraControl?.enabled === true; + if (!hasSettings) return null; return ( <> - {onCameraControlChange && ( - - - )} + + + {panel} - {cameraOpen && onCameraControlChange && ( - setCameraOpen(false)}> - setCameraOpen(false)} - onConfirm={(options, _prompt) => { - onCameraControlChange(options); - setCameraOpen(false); - }} - /> - - )} ); } diff --git a/web/src/components/canvas/canvas-image-toolbar-tools.tsx b/web/src/components/canvas/canvas-image-toolbar-tools.tsx index cd5519f13..8be18d5b4 100644 --- a/web/src/components/canvas/canvas-image-toolbar-tools.tsx +++ b/web/src/components/canvas/canvas-image-toolbar-tools.tsx @@ -159,7 +159,7 @@ const imageToolDefinitions: ImageToolDefinition[] = [ section: "视角", description: "调整光线方向、亮度与光效,生成新图片", icon: () => , - group: "viewpoint", + group: "lighting", order: 30, run: (node, handlers) => handlers.onLighting(node), }, diff --git a/web/src/components/canvas/canvas-node-prompt-panel.tsx b/web/src/components/canvas/canvas-node-prompt-panel.tsx index 1efc78c55..92ac442d8 100644 --- a/web/src/components/canvas/canvas-node-prompt-panel.tsx +++ b/web/src/components/canvas/canvas-node-prompt-panel.tsx @@ -1,7 +1,7 @@ import { Button, Image as AntImage, InputNumber, Modal, Popover } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { useEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react"; -import { ArrowUp, AtSign, Boxes, ChevronDown, FileText, ImageIcon, ImagePlus, Link2, LoaderCircle, Maximize2, Music2, Pencil, SlidersHorizontal, UserRound, Video, WandSparkles, X } from "lucide-react"; +import { ArrowUp, AtSign, Boxes, Camera, ChevronDown, FileText, ImageIcon, ImagePlus, Link2, LoaderCircle, Maximize2, Music2, Pencil, SlidersHorizontal, UserRound, Video, WandSparkles, X } from "lucide-react"; import { ModelPicker } from "@/components/model-picker"; import { defaultConfig, modelOptionName, resolveModelChannel, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store"; @@ -14,7 +14,10 @@ import { modelRequestOptions, resolveCompatibleModel, resolveModelGenerationDefa import { navigateToSettings } from "@/lib/settings-navigation"; import { useThemeStore } from "@/stores/use-theme-store"; import { useUserStore } from "@/stores/use-user-store"; +import { AppModal } from "@/components/ui/product/app-modal/app-modal"; +import type { CameraControlOptions } from "@/lib/canvas/camera-prompt-library"; import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover"; +import { CanvasNodeCameraPanel } from "./canvas-node-camera-dialog"; import { CanvasAudioSettingsPopover, type CanvasAudioSettingKey } from "./canvas-audio-settings-popover"; import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textarea"; import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover"; @@ -392,16 +395,23 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi /> ) : mode === "image" ? ( - onConfigChange(node.id, key === "count" ? { count: Number(value) || 1 } : { [key]: value })} - onMissingConfig={() => navigateToSettings({ continueCreation: true })} - onOpenChange={expanded ? undefined : onImageSettingsOpenChange} - cameraControl={node.metadata?.cameraControl} - onCameraControlChange={(options) => onConfigChange(node.id, { cameraControl: options })} - /> + // 图片模式下,显示相机配置与镜头配置 + <> + onConfigChange(node.id, { cameraControl: options })} + theme={theme} + compact={!expanded} + /> + onConfigChange(node.id, key === "count" ? { count: Number(value) || 1 } : { [key]: value })} + onMissingConfig={() => navigateToSettings({ continueCreation: true })} + onOpenChange={expanded ? undefined : onImageSettingsOpenChange} + /> + ) : mode === "video" ? ( void; theme: CanvasTheme; compact: boolean }) { + const [open, setOpen] = useState(false); + const cameraEnabled = cameraControl?.enabled === true; + return ( + <> + + {open ? ( + setOpen(false)}> + setOpen(false)} + onConfirm={(options) => { + onCameraControlChange(options); + setOpen(false); + }} + /> + + ) : null} + + ); +} + function ReferenceToolsPopover({ canAutoMention, autoLinkEnabled, onAutoMention, onAutoLinkEnabledChange, accent, compact }: { canAutoMention: boolean; autoLinkEnabled: boolean; onAutoMention: () => void; onAutoLinkEnabledChange: (enabled: boolean) => void; accent: string; compact: boolean }) { return ( - + {!compact ? 引用 : null} diff --git a/web/src/components/canvas/canvas-node-toolbar.tsx b/web/src/components/canvas/canvas-node-toolbar.tsx index 2c9951536..d3dd9d398 100644 --- a/web/src/components/canvas/canvas-node-toolbar.tsx +++ b/web/src/components/canvas/canvas-node-toolbar.tsx @@ -280,7 +280,8 @@ export function CanvasNodeToolbar({ const primaryTools = narrow ? primary.slice(0, 1) : primary; const portraitTools = compact ? [] : inGroup("portrait"); const viewpointTools = compact ? [] : inGroup("viewpoint"); - const processTools = compact ? [...inGroup("portrait"), ...inGroup("viewpoint"), ...inGroup("process")] : inGroup("process"); + const lightingTools = compact ? [] : inGroup("lighting"); + const processTools = compact ? [...inGroup("portrait"), ...inGroup("viewpoint"), ...inGroup("lighting"), ...inGroup("process")] : inGroup("process"); const workspaceTools = narrow ? [] : inGroup("workspace"); const utilityTools = inGroup("utility"); const moreTools = [...(narrow ? [...primary.slice(1), ...inGroup("workspace")] : []), ...inGroup("more")]; @@ -315,6 +316,7 @@ export function CanvasNodeToolbar({ {primaryTools.map((tool) => )} {portraitTools.length ? } tools={portraitTools} openMenuId={openMenuId} onOpenChange={handleMenuOpenChange} /> : null} {viewpointTools.map((tool) => )} + {lightingTools.map((tool) => )} {processTools.length ? : } tools={processTools} openMenuId={openMenuId} onOpenChange={handleMenuOpenChange} /> : null} {workspaceTools.length ? : null} {workspaceTools.map((tool) => )} diff --git a/web/src/lib/canvas/tool-registry/tool-definition.ts b/web/src/lib/canvas/tool-registry/tool-definition.ts index 74cf489b0..21f56eea9 100644 --- a/web/src/lib/canvas/tool-registry/tool-definition.ts +++ b/web/src/lib/canvas/tool-registry/tool-definition.ts @@ -7,7 +7,7 @@ import type { CanvasNodeData, CanvasNodeMetadata, CanvasNodeTypeId, CanvasToolMo /** 工具栏标识——每个工具栏有独立的注册表与偏好 */ export type ToolbarId = "main" | "selection" | "node-hover" | "add-node-menu"; -export type NodeToolbarGroup = "primary" | "portrait" | "viewpoint" | "process" | "workspace" | "utility" | "more"; +export type NodeToolbarGroup = "primary" | "portrait" | "viewpoint" | "lighting" | "process" | "workspace" | "utility" | "more"; /** 工具分类——用于分组渲染、危险隔离与 separator 自动插入 */ export type ToolCategory =