import { isValidElement, type ReactNode } from "react"; import { Brush, Camera, Clapperboard, Contrast, Copy, FastForward, FileText, Globe2, Grid2x2, Grid3x3, Layers3, Lock, LockOpen, Maximize2, Package, PencilLine, PersonStanding, Crop, Rewind, ScanFace, SlidersHorizontal, Smile, Sun, Upload, Scaling, WandSparkles } from "lucide-react"; import type { CanvasNodeData } from "@/types/canvas"; import type { NodeToolbarGroup } from "@/lib/canvas/tool-registry"; type ImageNodeActionToolId = "copyPrompt" | "reversePrompt" | "replace" | "resize" | "annotation" | "annotationEdit" | "textEdit" | "maskEdit" | "removeBackground" | "layerDecomposition" | "emotion" | "portraitTexture" | "crop" | "split" | "upscale" | "superResolve" | "angle" | "lighting" | "panorama" | "view" | "previs" | "multi_camera_nine_grid" | "story_pitch_four_grid" | "character_face_three_view" | "product_three_view" | "storyboard_25_grid" | "character_three_view_generation" | "cinematic_light_correction" | "image_projection_after_3s" | "image_projection_before_5s"; type ImageToolHandlers = { onUpload: (node: CanvasNodeData) => void; onToggleFreeResize: (node: CanvasNodeData) => void; onAnnotate: (node: CanvasNodeData) => void; onAnnotationEdit: (node: CanvasNodeData) => void; onTextEdit: (node: CanvasNodeData) => void; onMaskEdit: (node: CanvasNodeData) => void; onRemoveBackground: (node: CanvasNodeData) => void; onLayerDecomposition: (node: CanvasNodeData) => void; onEmotion: (node: CanvasNodeData) => void; onPortraitTexture: (node: CanvasNodeData) => void; onCrop: (node: CanvasNodeData) => void; onUpscale: (node: CanvasNodeData) => void; onSuperResolve: (node: CanvasNodeData) => void; onAngle: (node: CanvasNodeData) => void; onLighting: (node: CanvasNodeData) => void; onPanorama: (node: CanvasNodeData) => void; onPrevis: (node: CanvasNodeData) => void; onViewImage: (node: CanvasNodeData) => void; onCopyPrompt: (node: CanvasNodeData) => void; onReversePrompt: (node: CanvasNodeData) => void; onNineGrid: (node: CanvasNodeData, toolId: number, label: string, icon: string) => void; }; type ImageToolDefinition = { id: ImageNodeActionToolId; label: string | ((node: CanvasNodeData) => string); icon: (node: CanvasNodeData) => ReactNode; group: NodeToolbarGroup; order: number; section?: string; description?: string; active?: (node: CanvasNodeData) => boolean; run: (node: CanvasNodeData, handlers: ImageToolHandlers, tool: ImageToolDefinition) => void; /** 仅 group === "nine_grid" 时使用,对应后端工具 ID */ toolId?: number; }; /** lucide 组件把 PascalCase 图标名写入 displayName,从 icon 渲染结果反查图标名,未命中时回退 Grid3x3。 */ function resolveToolIconName(tool: ImageToolDefinition, node: CanvasNodeData) { const element = tool.icon(node); const icon_type = isValidElement(element) ? element.type : null; const name = icon_type && typeof icon_type !== "string" ? (icon_type as { displayName?: string }).displayName : undefined; return name || "Grid3x3"; } /** 九宫格工具共用行为:run 收到自身定义后直接取 toolId/label,图标名从 icon 反查。 */ function nineGridRun(node: CanvasNodeData, handlers: ImageToolHandlers, tool: ImageToolDefinition) { if (tool.toolId == null) return; handlers.onNineGrid(node, tool.toolId, resolveToolText(tool.label, node), resolveToolIconName(tool, node)); } const imageToolDefinitions: ImageToolDefinition[] = [ { id: "previs", label: "生成预演台", icon: () => , group: "primary", order: 20, section: "3D 预演", description: "让 Agent 识别当前图片并复现为可编辑的 3D 预演台", run: (node, handlers) => handlers.onPrevis(node), }, { id: "copyPrompt", section: "生成信息", label: "复制提示词", icon: () => , group: "more", order: 50, run: (node, handlers) => handlers.onCopyPrompt(node), }, { id: "reversePrompt", section: "生成信息", label: "反推提示词", icon: () => , group: "more", order: 60, run: (node, handlers) => handlers.onReversePrompt(node), }, { id: "replace", label: "替换图片", icon: () => , group: "more", section: "素材", order: 45, run: (node, handlers) => handlers.onUpload(node), }, { id: "resize", label: "锁定宽高比", section: "节点管理", active: (node) => !node.metadata?.freeResize, icon: (node) => (node.metadata?.freeResize ? : ), group: "more", order: 70, run: (node, handlers) => handlers.onToggleFreeResize(node), }, { id: "annotation", section: "拆分与标记", description: "添加标记,另存为新图片", label: "标注", icon: () => , group: "process", order: 40, run: (node, handlers) => handlers.onAnnotate(node), }, { id: "annotationEdit", section: "拆分与标记", description: "用画笔标记区域并让模型按标记修改", label: "标注编辑", icon: () => , group: "process", order: 45, run: (node, handlers) => handlers.onAnnotationEdit(node), }, { id: "maskEdit", label: "局部重绘", description: "涂抹要修改的区域,生成新图片", icon: () => , group: "primary", order: 10, run: (node, handlers) => handlers.onMaskEdit(node), }, { id: "textEdit", label: "文字编辑", description: "识别图片中的文字并逐行修改", icon: () => , group: "primary", order: 15, run: (node, handlers) => handlers.onTextEdit(node), }, { id: "removeBackground", label: "去除背景", description: "保留主体并生成透明背景图片", icon: () => , group: "process", order: 50, run: (node, handlers) => handlers.onRemoveBackground(node), }, { id: "layerDecomposition", label: "AI 图层拆分", description: "识别并拆分为多个可独立编辑的图片图层", icon: () => , group: "process", order: 55, run: (node, handlers) => handlers.onLayerDecomposition(node), }, { id: "emotion", label: "表情调整", section: "人像调整", description: "调整人物表情,生成新图片", icon: () => , group: "portrait", order: 50, run: (node, handlers) => handlers.onEmotion(node), }, { id: "portraitTexture", label: "质感调整", section: "人像调整", description: "设置肤质、光影与融合,再执行生成", icon: () => , group: "portrait", order: 60, run: (node, handlers) => handlers.onPortraitTexture(node), }, { id: "crop", label: "裁剪", section: "构图与尺寸", description: "保留所选区域,生成新图片", icon: () => , group: "process", order: 10, run: (node, handlers) => handlers.onCrop(node), }, { id: "split", label: "宫格切分", section: "拆分与标记", description: "按行列拆成多个图片节点", icon: () => , group: "process", order: 45, run: () => undefined, }, { id: "upscale", label: "调整尺寸", section: "构图与尺寸", description: "插值放大像素尺寸,不是 AI 超分", icon: () => , group: "process", order: 20, run: (node, handlers) => handlers.onUpscale(node), }, { id: "angle", label: "多角度", section: "视角", description: "调整摄影机角度,生成新视角", icon: () => , group: "viewpoint", order: 20, run: (node, handlers) => handlers.onAngle(node), }, { id: "lighting", label: "打光", section: "视角", description: "调整光线方向、亮度与光效,生成新图片", icon: () => , group: "lighting", order: 30, run: (node, handlers) => handlers.onLighting(node), }, { id: "view", label: "预览", icon: () => , group: "utility", order: 10, run: (node, handlers) => handlers.onViewImage(node), }, { id: "panorama", label: "全景图", section: "视角", description: "基于该图片创建 360° 全景查看节点", icon: () => , group: "panorama", order: 80, run: (node, handlers) => handlers.onPanorama(node), }, // 九宫格工具组——对应后端 nine_grid 种子数据 { id: "multi_camera_nine_grid", label: "多机位九宫格", section: "宫格生成", description: "生成 3x3 多机位联系表", icon: () => , group: "nine_grid", order: 10, toolId: 79, run: nineGridRun, }, { id: "story_pitch_four_grid", label: "剧情推演四宫格", section: "宫格生成", description: "生成 2x2 剧情推演联系表", icon: () => , group: "nine_grid", order: 20, toolId: 80, run: nineGridRun, }, { id: "character_face_three_view", label: "角色脸部三视图", section: "设定图", description: "生成 3x2 角色脸部联系表", icon: () => , group: "nine_grid", order: 30, toolId: 81, run: nineGridRun, }, { id: "product_three_view", label: "产品三视图", section: "设定图", description: "生成 3x2 产品联系表", icon: () => , group: "nine_grid", order: 40, toolId: 82, run: nineGridRun, }, { id: "storyboard_25_grid", label: "25宫格连贯分镜", section: "宫格生成", description: "生成 5x5 连贯分镜联系表", icon: () => , group: "nine_grid", order: 50, toolId: 83, run: nineGridRun, }, { id: "character_three_view_generation", label: "角色三视图", section: "设定图", description: "生成 16:9 角色三视图联系表", icon: () => , group: "nine_grid", order: 60, toolId: 85, run: nineGridRun, }, { id: "cinematic_light_correction", label: "电影级光影校正", section: "光影", description: "修正电影灯光,使场景更真实", icon: () => , group: "nine_grid", order: 70, toolId: 84, run: nineGridRun, }, { id: "image_projection_after_3s", label: "画面推演-3秒后", section: "画面推演", description: "生成 3 秒后的画面帧", icon: () => , group: "nine_grid", order: 80, toolId: 86, run: nineGridRun, }, { id: "image_projection_before_5s", label: "画面推演-5秒前", section: "画面推演", description: "生成 5 秒前的画面帧", icon: () => , group: "nine_grid", order: 90, toolId: 87, run: nineGridRun, }, ]; export function buildImageToolbarTools(node: CanvasNodeData, handlers: ImageToolHandlers) { return imageToolDefinitions.map((tool) => ({ id: tool.id, label: resolveToolText(tool.label, node), icon: tool.icon(node), group: tool.group, order: tool.order, section: tool.section, description: tool.description, active: tool.active?.(node), onClick: () => tool.run(node, handlers, tool), })); } function resolveToolText(value: string | ((node: CanvasNodeData) => string), node: CanvasNodeData) { return typeof value === "function" ? value(node) : value; } export type NineGridMenuItem = { id: string; label: string; section: string; description: string; toolId: number; toolIconName: string; }; /** 从 imageToolDefinitions 中筛选 nine_grid 组,返回九宫格菜单项数据 */ export function getNineGridMenuItems(): NineGridMenuItem[] { return imageToolDefinitions .filter((tool) => tool.group === "nine_grid" && tool.toolId != null) .sort((a, b) => a.order - b.order) .map((tool) => ({ id: tool.id, label: typeof tool.label === "function" ? tool.label({} as CanvasNodeData) : tool.label, section: tool.section || "常用操作", description: tool.description || "", toolId: tool.toolId!, toolIconName: resolveToolIconName(tool, {} as CanvasNodeData), })); }