From d68064f529767790794f1cc1b598f9dfb54ca31a Mon Sep 17 00:00:00 2001 From: Stringadmin Date: Fri, 5 Jun 2026 18:15:29 +0800 Subject: [PATCH] refactor: share canvas mention textarea --- src/features/canvas/CanvasPage.tsx | 138 +++------------- .../canvas/CanvasTextPromptComposer.tsx | 153 +++++++++++------- 2 files changed, 122 insertions(+), 169 deletions(-) diff --git a/src/features/canvas/CanvasPage.tsx b/src/features/canvas/CanvasPage.tsx index 5597763..c651b21 100644 --- a/src/features/canvas/CanvasPage.tsx +++ b/src/features/canvas/CanvasPage.tsx @@ -186,7 +186,7 @@ import { } from "./canvasWorkflowDeserialize"; import { CanvasNodeToolbar, CanvasNodeVideoPlayer, CanvasSelectChip } from "./canvasComponents"; import type { CanvasNodeToolbarAction } from "./canvasComponents"; -import { CanvasTextPromptComposer } from "./CanvasTextPromptComposer"; +import { CanvasPromptMentionTextarea, CanvasTextPromptComposer } from "./CanvasTextPromptComposer"; import { CanvasMultiGridPanel, CanvasUpscalePanel, CanvasInpaintPanel } from "./canvasToolPanels"; import { CanvasSmoothedProgressRing } from "./CanvasSmoothedProgressRing"; @@ -198,7 +198,6 @@ const canvasEnterpriseVideoModelOptions: CanvasOption[] = ENTERPRISE_VIDEO_MODEL // --- Canvas generation keep-alive (survives page refresh / view switch) --- const MENTION_TOKEN_RE = /@(?:图片|视频|文本)\d+/g; -const MENTION_BOUNDARY_RE = /\s|[,。、;:!??(){}[\]<>]/; function buildNodeMentionOptions( kind: CanvasNodeKind, @@ -4434,38 +4433,7 @@ function CanvasPage({ ) : null} - {imageNodeActive && !isCanvasNodeMoving && !imageNodeFocusActive ? (() => { - const imgMentionOptions = buildNodeMentionOptions("image", imageNode.id, imageNodes, videoNodes, textNodes, getIncomingCanvasPorts, manualLinks); - const imgMentionState = textNodeMentionStates[imageNode.id] || { open: false, query: "", start: 0, caret: 0, activeIndex: 0 }; - const imgFilteredMentions = imgMentionState.open - ? imgMentionOptions.filter((o) => !imgMentionState.query || o.searchText.includes(imgMentionState.query.toLowerCase())) - : []; - - const handleImagePromptChange = (e: React.ChangeEvent) => { - const value = e.target.value; - const caret = e.target.selectionStart || 0; - updateImageNodePrompt(imageNode.id, value); - const beforeCaret = value.slice(0, caret); - const atIdx = beforeCaret.lastIndexOf("@"); - if (atIdx >= 0) { - const query = beforeCaret.slice(atIdx + 1); - if (!MENTION_BOUNDARY_RE.test(query) && !query.includes(" ")) { - setTextNodeMentionStates((prev) => ({ ...prev, [imageNode.id]: { open: true, query, start: atIdx, caret, activeIndex: 0 } })); - return; - } - } - closeTextNodeMention(imageNode.id); - }; - - const handleImagePromptKeyDown = (e: React.KeyboardEvent) => { - if (!imgMentionState.open || imgFilteredMentions.length === 0) return; - if (e.key === "ArrowDown") { e.preventDefault(); setTextNodeMentionStates((prev) => ({ ...prev, [imageNode.id]: { ...imgMentionState, activeIndex: (imgMentionState.activeIndex + 1) % imgFilteredMentions.length } })); } - else if (e.key === "ArrowUp") { e.preventDefault(); setTextNodeMentionStates((prev) => ({ ...prev, [imageNode.id]: { ...imgMentionState, activeIndex: (imgMentionState.activeIndex - 1 + imgFilteredMentions.length) % imgFilteredMentions.length } })); } - else if (e.key === "Enter" || e.key === "Tab") { e.preventDefault(); const opt = imgFilteredMentions[imgMentionState.activeIndex]; if (opt) insertTextNodeMention(imageNode.id, opt, e.currentTarget, "image"); } - else if (e.key === "Escape") { e.preventDefault(); closeTextNodeMention(imageNode.id); } - }; - - return ( + {imageNodeActive && !isCanvasNodeMoving && !imageNodeFocusActive ? (
-
-