fix(canvas): 图片工具 - 移除独立图片编辑入口并保留连线生成
Co-authored-by: lmj15657306288-ui <lmj15657306288-ui@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { Brush, Camera, Copy, FileText, Globe2, Grid2x2, Layers3, Lock, LockOpen
|
||||
import type { CanvasNodeData } from "@/types/canvas";
|
||||
import type { NodeToolbarGroup } from "@/lib/canvas/tool-registry";
|
||||
|
||||
type ImageNodeActionToolId = "copyPrompt" | "reversePrompt" | "replace" | "resize" | "annotation" | "annotationEdit" | "textEdit" | "maskEdit" | "imageEdit" | "removeBackground" | "layerDecomposition" | "emotion" | "portraitTexture" | "crop" | "split" | "upscale" | "superResolve" | "angle" | "lighting" | "panorama" | "view";
|
||||
type ImageNodeActionToolId = "copyPrompt" | "reversePrompt" | "replace" | "resize" | "annotation" | "annotationEdit" | "textEdit" | "maskEdit" | "removeBackground" | "layerDecomposition" | "emotion" | "portraitTexture" | "crop" | "split" | "upscale" | "superResolve" | "angle" | "lighting" | "panorama" | "view";
|
||||
|
||||
type ImageToolHandlers = {
|
||||
onUpload: (node: CanvasNodeData) => void;
|
||||
@@ -13,7 +13,6 @@ type ImageToolHandlers = {
|
||||
onAnnotationEdit: (node: CanvasNodeData) => void;
|
||||
onTextEdit: (node: CanvasNodeData) => void;
|
||||
onMaskEdit: (node: CanvasNodeData) => void;
|
||||
onImageEdit: (node: CanvasNodeData) => void;
|
||||
onRemoveBackground: (node: CanvasNodeData) => void;
|
||||
onLayerDecomposition: (node: CanvasNodeData) => void;
|
||||
onEmotion: (node: CanvasNodeData) => void;
|
||||
@@ -117,15 +116,6 @@ const imageToolDefinitions: ImageToolDefinition[] = [
|
||||
order: 15,
|
||||
run: (node, handlers) => handlers.onTextEdit(node),
|
||||
},
|
||||
{
|
||||
id: "imageEdit",
|
||||
label: "图片编辑",
|
||||
description: "用文字描述要修改的内容,生成新图片",
|
||||
icon: () => <WandSparkles className="size-3.5" />,
|
||||
group: "primary",
|
||||
order: 20,
|
||||
run: (node, handlers) => handlers.onImageEdit(node),
|
||||
},
|
||||
{
|
||||
id: "removeBackground",
|
||||
label: "去除背景",
|
||||
|
||||
@@ -31,7 +31,7 @@ export function CanvasNodeImageEditDialog({
|
||||
}, [config, open, dataUrl]);
|
||||
|
||||
return (
|
||||
<Modal open={open && Boolean(dataUrl)} onCancel={onClose} footer={null} centered destroyOnHidden width={860} title="图片编辑">
|
||||
<Modal open={open && Boolean(dataUrl)} onCancel={onClose} footer={null} centered destroyOnHidden width={860} title={preset === "remove-background" ? "去除背景" : "图片编辑"}>
|
||||
<div className="grid gap-5 md:grid-cols-[minmax(0,1fr)_300px]">
|
||||
<div className="grid min-h-[320px] place-items-center overflow-hidden rounded-xl bg-black/5 p-3 dark:bg-white/[0.04]">
|
||||
<img src={dataUrl} alt="待编辑图片" className="max-h-[58vh] max-w-full object-contain" draggable={false} />
|
||||
|
||||
@@ -37,7 +37,6 @@ type CanvasNodeToolbarProps = {
|
||||
onDownload: (node: CanvasNodeData) => void;
|
||||
onSaveAsset: (node: CanvasNodeData) => void;
|
||||
onMaskEdit: (node: CanvasNodeData) => void;
|
||||
onImageEdit: (node: CanvasNodeData) => void;
|
||||
onRemoveBackground: (node: CanvasNodeData) => void;
|
||||
onLayerDecomposition: (node: CanvasNodeData) => void;
|
||||
onEmotion: (node: CanvasNodeData) => void;
|
||||
@@ -103,7 +102,6 @@ export function CanvasNodeToolbar({
|
||||
onDownload,
|
||||
onSaveAsset,
|
||||
onMaskEdit,
|
||||
onImageEdit,
|
||||
onRemoveBackground,
|
||||
onLayerDecomposition,
|
||||
onEmotion,
|
||||
@@ -231,13 +229,13 @@ export function CanvasNodeToolbar({
|
||||
}
|
||||
copyText(prompt, "提示词已复制");
|
||||
};
|
||||
const imageTools = buildImageToolbarTools(node, { onUpload, onToggleFreeResize, onAnnotate, onAnnotationEdit, onTextEdit, onMaskEdit, onImageEdit, onRemoveBackground, onLayerDecomposition, onEmotion, onPortraitTexture, onCrop, onUpscale, onSuperResolve, onAngle, onLighting, onPanorama, onViewImage, onCopyPrompt: copyImagePrompt, onReversePrompt });
|
||||
const imageTools = buildImageToolbarTools(node, { onUpload, onToggleFreeResize, onAnnotate, onAnnotationEdit, onTextEdit, onMaskEdit, onRemoveBackground, onLayerDecomposition, onEmotion, onPortraitTexture, onCrop, onUpscale, onSuperResolve, onAngle, onLighting, onPanorama, onViewImage, onCopyPrompt: copyImagePrompt, onReversePrompt });
|
||||
|
||||
// 构建 ToolContext——供注册表解析工具
|
||||
const nodeHoverHandlers = {
|
||||
onNodeInfo: onInfo, onNodeDelete: onDelete, onNodeRetry: onRetry, onNodeEditText: onEditText, onNodeDecreaseFont: onDecreaseFont, onNodeIncreaseFont: onIncreaseFont,
|
||||
onNodeToggleDialog: onToggleDialog, onNodeAnnotate: onAnnotate, onNodeGenerateImage: onGenerateImage, onNodeUpload: onUpload, onNodeDownload: onDownload,
|
||||
onNodeSaveAsset: onSaveAsset, onNodeMaskEdit: onMaskEdit, onNodeImageEdit: onImageEdit, onNodeRemoveBackground: onRemoveBackground, onNodeEmotion: onEmotion, onNodePortraitTexture: onPortraitTexture, onNodeCrop: onCrop,
|
||||
onNodeSaveAsset: onSaveAsset, onNodeMaskEdit: onMaskEdit, onNodeRemoveBackground: onRemoveBackground, onNodeEmotion: onEmotion, onNodePortraitTexture: onPortraitTexture, onNodeCrop: onCrop,
|
||||
onNodeSplit: (target) => onSplit(target, { rows: 2, columns: 2 }), onNodeUpscale: onUpscale, onNodeSuperResolve: onSuperResolve, onNodeAngle: onAngle, onNodeViewImage: onViewImage,
|
||||
onNodeExtractVideoFrames: onExtractVideoFrames, onNodeExtractAudioFromVideo: onExtractAudioFromVideo, onNodeTrimVideoSegments: onTrimVideoSegments, onNodeReversePrompt: onReversePrompt, onNodeToggleFreeResize: onToggleFreeResize,
|
||||
onNodeSubtitles: onSubtitles, onNodeTimeline: onTimeline, onNodeToggleLocked: onToggleLocked, onNodeCopyPrompt: copyImagePrompt,
|
||||
|
||||
@@ -31,7 +31,7 @@ type CanvasProjectMediaDialogsProps = {
|
||||
onAnnotationEdit: (node: CanvasNodeData, payload: CanvasImageAnnotationPayload) => void;
|
||||
onMaskEdit: (node: CanvasNodeData, payload: CanvasImageMaskEditPayload) => void;
|
||||
onUpscale: (node: CanvasNodeData, params: CanvasImageUpscaleParams) => void;
|
||||
onImageEdit: (node: CanvasNodeData, payload: CanvasImageEditPayload) => void;
|
||||
onImageOperation: (node: CanvasNodeData, payload: CanvasImageEditPayload) => void;
|
||||
onLayerDecomposition: (node: CanvasNodeData, payload: CanvasImageLayerDecompositionPayload) => void;
|
||||
onDetectText: () => Promise<import("@/components/canvas/canvas-node-text-edit-dialog").CanvasImageTextLine[]>;
|
||||
onTextEdit: (node: CanvasNodeData, payload: CanvasImageTextEditPayload) => void;
|
||||
@@ -61,7 +61,7 @@ export function CanvasProjectMediaDialogs({
|
||||
onAnnotationEdit,
|
||||
onMaskEdit,
|
||||
onUpscale,
|
||||
onImageEdit,
|
||||
onImageOperation,
|
||||
onLayerDecomposition,
|
||||
onDetectText,
|
||||
onTextEdit,
|
||||
@@ -74,7 +74,7 @@ export function CanvasProjectMediaDialogs({
|
||||
{annotationEditNode?.metadata?.content ? <CanvasNodeAnnotationDialog image={{ url: annotationEditNode.metadata.content, storageKey: annotationEditNode.metadata.storageKey }} editMode open onClose={onCloseAnnotationEdit} onConfirm={(payload) => { if (typeof payload !== "string") onAnnotationEdit(annotationEditNode, payload); }} /> : null}
|
||||
{maskEditNode?.metadata?.content ? <CanvasNodeMaskEditDialog dataUrl={maskEditNode.metadata.content} config={{ ...config, model: maskEditNode.metadata.model || config.model, imageModel: maskEditNode.metadata.model || config.imageModel, size: maskEditNode.metadata.size || config.size, quality: maskEditNode.metadata.quality || config.quality, count: String(maskEditNode.metadata.count || config.count) }} open onClose={onCloseMaskEdit} onConfirm={(payload) => onMaskEdit(maskEditNode, payload)} /> : null}
|
||||
{upscaleNode?.metadata?.content ? <CanvasNodeUpscaleDialog dataUrl={upscaleNode.metadata.content} open onClose={onCloseUpscale} onConfirm={(params) => onUpscale(upscaleNode, params)} /> : null}
|
||||
{imageEditNode?.metadata?.content ? <CanvasNodeImageEditDialog dataUrl={imageEditNode.metadata.content} preset={imageEditPreset} config={{ ...config, model: imageEditNode.metadata.model || config.model, imageModel: imageEditNode.metadata.model || config.imageModel, size: imageEditNode.metadata.size || config.size, quality: imageEditNode.metadata.quality || config.quality }} open onClose={onCloseImageEdit} onConfirm={(payload) => onImageEdit(imageEditNode, payload)} /> : null}
|
||||
{imageEditNode?.metadata?.content ? <CanvasNodeImageEditDialog dataUrl={imageEditNode.metadata.content} preset={imageEditPreset} config={{ ...config, model: imageEditNode.metadata.model || config.model, imageModel: imageEditNode.metadata.model || config.imageModel, size: imageEditNode.metadata.size || config.size, quality: imageEditNode.metadata.quality || config.quality }} open onClose={onCloseImageEdit} onConfirm={(payload) => onImageOperation(imageEditNode, payload)} /> : null}
|
||||
{layerDecompositionNode?.metadata?.content ? <CanvasNodeLayerDecompositionDialog dataUrl={layerDecompositionNode.metadata.content} config={{ ...config, model: layerDecompositionNode.metadata.model || config.model, imageModel: layerDecompositionNode.metadata.model || config.imageModel, size: layerDecompositionNode.metadata.size || config.size, quality: layerDecompositionNode.metadata.quality || config.quality }} open onClose={onCloseLayerDecomposition} onConfirm={(payload) => onLayerDecomposition(layerDecompositionNode, payload)} /> : null}
|
||||
{textEditNode?.metadata?.content ? <CanvasNodeTextEditDialog dataUrl={textEditNode.metadata.content} open onClose={onCloseTextEdit} onDetect={onDetectText} onConfirm={(payload) => onTextEdit(textEditNode, payload)} /> : null}
|
||||
</>
|
||||
|
||||
@@ -875,7 +875,6 @@ function InfiniteCanvasPage() {
|
||||
setMaskEditNodeId,
|
||||
setImageEditNodeId,
|
||||
setImageEditPreset,
|
||||
openImageEditNode,
|
||||
openBackgroundRemoval,
|
||||
openLayerDecomposition,
|
||||
decomposeImageLayers,
|
||||
@@ -2171,7 +2170,6 @@ function InfiniteCanvasPage() {
|
||||
onFocusOutput={(nodeId) => focusCanvasImageNode(nodeId)}
|
||||
onReorderReferenceColumns={(fromColumnId, toColumnId) => reorderBatchReferenceColumns(contentNode.id, fromColumnId, toColumnId)}
|
||||
onMoveReferenceCell={(sourceRowId, sourceColumnIndex, targetRowId, targetColumnIndex) => moveBatchReferenceCell(contentNode.id, sourceRowId, sourceColumnIndex, targetRowId, targetColumnIndex)}
|
||||
onReplaceReference={replaceCanvasNodeMedia}
|
||||
onUploadReference={(rowId, columnIndex, file) => { void handleUploadBatchReference(contentNode.id, rowId, columnIndex, file); }}
|
||||
onConnectStart={(event, handleId) => handleConnectStart(event, contentNode.id, "target", handleId)}
|
||||
onConnectDrop={(event, handleId) => handleConnectDrop(event, contentNode.id, handleId)}
|
||||
@@ -2838,7 +2836,6 @@ function InfiniteCanvasPage() {
|
||||
onAnnotate={(node) => setAnnotationNodeId(node.id)}
|
||||
onAnnotationEdit={openAnnotationEditNode}
|
||||
onMaskEdit={(node) => setMaskEditNodeId(node.id)}
|
||||
onImageEdit={openImageEditNode}
|
||||
onRemoveBackground={openBackgroundRemoval}
|
||||
onLayerDecomposition={openLayerDecomposition}
|
||||
onTextEdit={openTextEditNode}
|
||||
@@ -3181,7 +3178,7 @@ function InfiniteCanvasPage() {
|
||||
onAnnotate={(node, dataUrl) => void saveAnnotatedImageNode(node, dataUrl)}
|
||||
onAnnotationEdit={(node, payload) => void editAnnotatedImageNode(node, payload)}
|
||||
onMaskEdit={(node, payload) => void maskEditImageNode(node, payload)}
|
||||
onImageEdit={(node, payload) => void editImageNode(node, payload)}
|
||||
onImageOperation={(node, payload) => void editImageNode(node, payload)}
|
||||
onLayerDecomposition={(node, payload) => void decomposeImageLayers(node, payload)}
|
||||
onDetectText={() => {
|
||||
const node = textEditNodeId ? nodeById.get(textEditNodeId) : null;
|
||||
|
||||
@@ -282,7 +282,7 @@ export default function SharedCanvasPage() {
|
||||
}} onHoverStart={keepToolbar} onHoverEnd={hideToolbar} onConnectStart={unauthorized} onResize={() => undefined} onContentChange={unauthorized} onRetry={unauthorized} onOpenTaskDetails={unauthorized} onViewImage={(target) => setInfoNodeId(target.id)} onContextMenu={(event, nodeId) => openContextMenu(event, nodeId)} />)}
|
||||
</InfiniteCanvas>
|
||||
|
||||
<CanvasNodeToolbar node={dragRef.current ? null : toolbarNode} viewport={viewport} containerRef={containerRef} onKeep={keepToolbar} onLeave={hideToolbar} onInfo={(node) => setInfoNodeId(node.id)} onEditText={unauthorized} onDecreaseFont={unauthorized} onIncreaseFont={unauthorized} onToggleDialog={unauthorized} onAnnotate={unauthorized} onAnnotationEdit={unauthorized} onTextEdit={unauthorized} onGenerateImage={unauthorized} onUpload={unauthorized} onDownload={unauthorized} onSaveAsset={unauthorized} onMaskEdit={unauthorized} onImageEdit={unauthorized} onRemoveBackground={unauthorized} onLayerDecomposition={unauthorized} onEmotion={unauthorized} onPortraitTexture={unauthorized} onCrop={unauthorized} onSplit={unauthorized} onUpscale={unauthorized} onSuperResolve={unauthorized} onAngle={unauthorized} onLighting={unauthorized} onPanorama={unauthorized} onViewImage={unauthorized} onExtractVideoFrames={unauthorized} onExtractAudioFromVideo={unauthorized} onTrimVideoSegments={unauthorized} extractingVideoFrames={false} extractingAudio={false} trimmingVideo={false} onSubtitles={unauthorized} onTimeline={unauthorized} onReversePrompt={unauthorized} onRetry={unauthorized} onToggleFreeResize={unauthorized} onToggleLocked={unauthorized} onDelete={unauthorized} />
|
||||
<CanvasNodeToolbar node={dragRef.current ? null : toolbarNode} viewport={viewport} containerRef={containerRef} onKeep={keepToolbar} onLeave={hideToolbar} onInfo={(node) => setInfoNodeId(node.id)} onEditText={unauthorized} onDecreaseFont={unauthorized} onIncreaseFont={unauthorized} onToggleDialog={unauthorized} onAnnotate={unauthorized} onAnnotationEdit={unauthorized} onTextEdit={unauthorized} onGenerateImage={unauthorized} onUpload={unauthorized} onDownload={unauthorized} onSaveAsset={unauthorized} onMaskEdit={unauthorized} onRemoveBackground={unauthorized} onLayerDecomposition={unauthorized} onEmotion={unauthorized} onPortraitTexture={unauthorized} onCrop={unauthorized} onSplit={unauthorized} onUpscale={unauthorized} onSuperResolve={unauthorized} onAngle={unauthorized} onLighting={unauthorized} onPanorama={unauthorized} onViewImage={unauthorized} onExtractVideoFrames={unauthorized} onExtractAudioFromVideo={unauthorized} onTrimVideoSegments={unauthorized} extractingVideoFrames={false} extractingAudio={false} trimmingVideo={false} onSubtitles={unauthorized} onTimeline={unauthorized} onReversePrompt={unauthorized} onRetry={unauthorized} onToggleFreeResize={unauthorized} onToggleLocked={unauthorized} onDelete={unauthorized} />
|
||||
|
||||
<div className="absolute bottom-5 left-5 z-[var(--z-panel-floating)]"><CanvasZoomControls scale={viewport.k} containerRef={containerRef} onScaleChange={setZoom} onFitContent={resetViewport} isMiniMapOpen={false} onToggleMiniMap={unauthorized} onOpenShortcuts={unauthorized} /></div>
|
||||
<div className="pointer-events-none absolute bottom-5 right-5 z-[var(--z-panel-floating)] max-w-[340px] text-right text-xs leading-5" style={{ color: theme.node.muted }}>访客操作仅在当前页面临时生效</div>
|
||||
|
||||
@@ -962,11 +962,6 @@ export function useCanvasMediaTools({
|
||||
}
|
||||
}, [bindGenerationTask, effectiveConfig, finishGenerationRequest, isAiConfigReady, message, nodesRef, persistMediaNodes, projectId, setConnections, setDialogNodeId, setNodes, setRunningNodeId, setSelectedConnectionId, setSelectedNodeIds, startGenerationRequest]);
|
||||
|
||||
const openImageEditNode = useCallback((node: CanvasNodeData) => {
|
||||
setImageEditPreset(null);
|
||||
setImageEditNodeId(node.id);
|
||||
}, []);
|
||||
|
||||
const openBackgroundRemoval = useCallback((node: CanvasNodeData) => {
|
||||
setImageEditPreset("remove-background");
|
||||
setImageEditNodeId(node.id);
|
||||
@@ -1247,7 +1242,6 @@ export function useCanvasMediaTools({
|
||||
setMaskEditNodeId,
|
||||
setImageEditNodeId,
|
||||
setImageEditPreset,
|
||||
openImageEditNode,
|
||||
openBackgroundRemoval,
|
||||
openLayerDecomposition,
|
||||
decomposeImageLayers,
|
||||
|
||||
Reference in New Issue
Block a user