From 0198458551511da5266db466cfe09e14e9036734 Mon Sep 17 00:00:00 2001 From: kinoko <1771634408@qq.com> Date: Wed, 9 Sep 2026 14:26:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E6=99=BA=E8=83=BD=E5=BC=95?= =?UTF-8?q?=E7=94=A8=20-=20=E6=94=AF=E6=8C=81=E7=B4=A0=E6=9D=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=A1=A5=E5=85=A8=E4=B8=8E=E5=85=A8=E6=96=87=E5=BC=95?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/content/docs/overview/features.mdx | 6 + docs/content/docs/progress/pending-test.mdx | 7 + .../canvas/canvas-node-prompt-panel.tsx | 82 +++++++++++- .../canvas-resource-mention-textarea.tsx | 124 ++++++++++++++++-- .../lib/canvas/canvas-resource-references.ts | 99 ++++++++++++++ web/src/styles/globals.css | 46 +++++++ web/test/canvas-resource-references.test.ts | 54 +++++++- 7 files changed, 402 insertions(+), 16 deletions(-) diff --git a/docs/content/docs/overview/features.mdx b/docs/content/docs/overview/features.mdx index 1dba246cf..a8b9bc663 100644 --- a/docs/content/docs/overview/features.mdx +++ b/docs/content/docs/overview/features.mdx @@ -5,6 +5,12 @@ description: 当前项目已实现的核心工作台能力与使用入口 # 功能清单 +## 画布智能引用 + +节点提示词面板提供“智能引用”入口,支持 AutoLink 开关和“一键引用全文”。输入已连接素材的名称、标签或素材栏序号后,按 Tab 或点击光标旁的候选项插入引用;全文引用会批量转换匹配到的指代,没有可转换内容时按钮禁用。 + +匹配仅覆盖当前已连接且激活的画布素材,跳过技能、素材库条目、重名歧义和已有引用。支持 `图1`、`图片1`、`image1`、`image 1` 及独立序号;普通数字文本如“约2秒”保持原样。AutoLink 默认在节点面板开启,开关为面板临时状态。 + ## 画布抓手与框选 画布底部主工具栏用一个胶囊开关切换「区域选择」和「抓手工具」。默认是区域选择,空白处拖动框选节点;切到抓手后拖动平移画布。按住空格或中键仍可临时平移,不改变当前工具。 diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 5d7656f3c..3b1f2ddf7 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,13 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## 画布智能引用 + +- [ ] 在简洁、专业模式及展开提示词面板中打开智能引用,检查明暗主题、弹层宽度和开关状态。 +- [ ] 输入素材名称、`image1` 或独立序号,按 Tab 和点击候选项均能插入正确引用;关闭 AutoLink 后不再提示。 +- [ ] 一键引用全文保留重复指代,跳过已有引用、重名和“约2秒”等普通文本;无匹配时按钮禁用。 +- [ ] 长提示词滚动、移动光标、中文输入和切换节点后,引用插入位置正确;素材原有双击预览正常。 + ## v1.2.8.rc1 候选版本发布 - 发布版本与镜像标签分别为 `v1.2.8.rc1` 和 `1.2.8.rc1`;GitHub Release 应标记为预发布,不能被当作正式版本推荐,也不覆盖已发布的 `latest`。 diff --git a/web/src/components/canvas/canvas-node-prompt-panel.tsx b/web/src/components/canvas/canvas-node-prompt-panel.tsx index fdb6d73df..a850e414c 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 } from "antd"; +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, Boxes, ChevronDown, FileText, ImageIcon, ImagePlus, LoaderCircle, Maximize2, Music2, Pencil, SlidersHorizontal, UserRound, Video, WandSparkles, X } from "lucide-react"; +import { ArrowUp, AtSign, Boxes, 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"; @@ -23,7 +23,7 @@ import { CanvasPresetPicker, type CanvasPromptPreset } from "./canvas-preset-pic import { CanvasPortraitTexturePopover } from "./canvas-portrait-texture-popover"; import { CanvasPromptOptimizerDrawer } from "./canvas-prompt-optimizer-drawer"; import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData, type CanvasNodeMetadata, type CanvasWorkspaceMode } from "@/types/canvas"; -import { canvasResourceMentionToken, normalizeCanvasNodeMentionTokens, type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; +import { autoMentionCanvasResourceReferences, canvasResourceMentionToken, normalizeCanvasNodeMentionTokens, type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; import { promptOptimizerPlugin, PROMPT_OPTIMIZER_PLUGIN_ID } from "@/lib/plugins/builtin/prompt-optimizer"; import { createPluginHostContext } from "@/services/plugin-host"; import { usePluginStore } from "@/stores/use-plugin-store"; @@ -79,6 +79,7 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi const [manualExpandedPromptHeight, setManualExpandedPromptHeight] = useState(null); const [paramsExpanded, setParamsExpanded] = useState(false); // #98 决策2:B区参数区折叠状态(手风琴) const [promptOptimizerOpen, setPromptOptimizerOpen] = useState(false); + const [autoLinkEnabled, setAutoLinkEnabled] = useState(true); const resolvedMentionReferences = useResolvedCanvasResourceReferences(mentionReferences); const normalizedSavedPrompt = useMemo(() => normalizeCanvasNodeMentionTokens(savedPrompt, mentionReferences), [mentionReferences, savedPrompt]); const activeReferences = resolvedMentionReferences.filter((item) => item.active && item.kind !== "skill"); @@ -159,6 +160,8 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi const canExpandPrompt = mode === "image" || mode === "video"; const canOptimizePrompt = Boolean(promptOptimizerProvider) && canExpandPrompt; const isPortraitTexture = mode === "image" && Boolean(node.metadata?.portraitTexture); + const autoMentionedPrompt = useMemo(() => autoMentionCanvasResourceReferences(prompt, resolvedMentionReferences), [prompt, resolvedMentionReferences]); + const canAutoMention = autoMentionedPrompt !== prompt; useEffect(() => { setPrompt(normalizedSavedPrompt); @@ -339,6 +342,14 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi {activeReferenceCount ? `已连接 ${activeReferenceCount} 个素材` : "将使用默认模型与参数"} + updatePrompt(autoMentionedPrompt)} + onAutoLinkEnabledChange={setAutoLinkEnabled} + accent={monochromeAccent} + compact + /> {renderSubmitButton(expanded)} ) : ( @@ -360,6 +371,14 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi />
+ updatePrompt(autoMentionedPrompt)} + onAutoLinkEnabledChange={setAutoLinkEnabled} + accent={monochromeAccent} + compact={!expanded} + /> {mode === "text" ? ( void; onAutoLinkEnabledChange: (enabled: boolean) => void; accent: string; compact: boolean }) { + return ( + +
+
智能引用
+
输入素材序号或名称后按 Tab,可快速引用
+
+
+
AutoLink
+ +
+ +
+ } + > + + + ); +} + function GenerationModeIcon({ mode }: { mode: CanvasNodeGenerationMode }) { if (mode === "image") return ; if (mode === "video") return