feat: add local edit to infinite canvas
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-19 13:11:53 +08:00
parent ac70694ef3
commit 2d59e3e3c4
5 changed files with 909 additions and 19 deletions

View File

@ -956,6 +956,7 @@ const CANVAS_TOOLBAR_FALLBACK_ICONS: Record<CanvasToolbarItemKey, LucideIcon> =
imageEnhance: Sparkles,
imageRemoveBackground: Scissors,
imageBoxCutout: Scan,
imageLocalEdit: Wand2,
imageRemoveWatermark: Eraser,
imageResize: Maximize2,
imageTextEdit: Type,

View File

@ -33,6 +33,7 @@ import {
Tag,
type LucideIcon,
UserRound,
Wand2,
Zap,
} from "lucide-react";
import {
@ -45,6 +46,7 @@ import {
} from "@/lib/canvas/video-canvas-markers";
import {
getCanvasToolbarItem,
isCanvasToolbarItemEnabled,
normalizeCanvasToolbarConfig,
type CanvasToolbarConfig,
} from "@/lib/canvas-toolbar-config";
@ -373,6 +375,14 @@ export function CanvasWorkspace({
),
[nodes, selectedNodeIds]
);
const selectedImageLocalEditItem = getCanvasToolbarItem(
canvasToolbarConfig,
"imageLocalEdit"
);
const selectedImageLocalEditEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageLocalEdit"
);
// Store actions
const setViewport = useCanvasStore((state) => state.setViewport);
@ -386,6 +396,9 @@ export function CanvasWorkspace({
const resizeNode = useCanvasStore((state) => state.resizeNode);
const pushHistory = useCanvasStore((state) => state.pushHistory);
const updateNode = useCanvasStore((state) => state.updateNode);
const requestImageNodeAction = useCanvasStore(
(state) => state.requestImageNodeAction
);
const clearLastAddedNodeId = useCanvasStore((state) => state.clearLastAddedNodeId);
const centerTimeoutRef = useRef<number | null>(null);
const isMacPlatform = React.useMemo(() => {
@ -1870,9 +1883,34 @@ export function CanvasWorkspace({
</div>
)}
{/* 缩放控制器 */}
{/* 选中图片快捷操作 */}
{selectedImageNodes.length > 0 && (
<div className="absolute bottom-16 left-4 z-30" data-canvas-overlay>
<div
className="absolute bottom-16 left-4 z-30 flex items-center gap-2"
data-canvas-overlay
>
{selectedImageLocalEditEnabled && selectedImageNodes.length === 1 ? (
<Button
variant={isWorkflowCanvas ? "outline" : "secondary"}
size="sm"
className={cn(
"h-8 gap-1.5 px-3 text-xs shadow-sm border border-border/60",
isWorkflowCanvas &&
"rounded-full border-[#dbe2ee] bg-white text-[#3b4860] shadow-[0_0_0_1px_rgba(219,226,238,0.26)] hover:bg-[#f7f9ff]"
)}
onClick={() =>
requestImageNodeAction(selectedImageNodes[0].id, "local-edit")
}
title={selectedImageLocalEditItem.label}
>
<CanvasToolbarIcon
className="h-3.5 w-3.5"
fallback={Wand2}
item={selectedImageLocalEditItem}
/>
{selectedImageLocalEditItem.label}
</Button>
) : null}
<Button
variant={isWorkflowCanvas ? "outline" : "secondary"}
size="sm"

File diff suppressed because it is too large Load Diff

View File

@ -205,6 +205,12 @@ export const DEFAULT_CANVAS_TOOLBAR_ITEMS = [
label: "框选抠图",
description: "框选区域后抠图",
},
{
key: "imageLocalEdit",
group: "节点工具栏",
label: "局部编辑",
description: "框选多个彩色区域并按区域提示词局部改图",
},
{
key: "imageRemoveWatermark",
group: "节点工具栏",

View File

@ -71,7 +71,7 @@ export interface PickedObject {
};
}
export type ImageNodeActionType = 'box-cutout' | 'text-edit';
export type ImageNodeActionType = 'box-cutout' | 'text-edit' | 'local-edit';
export interface ImageNodeActionRequest {
nodeId: string;