// 节点提示词面板的参考素材区:已连接参考的缩略图货架、自动 @ 引用工具与拖拽排序。 import { Image as AntImage, Popover } from "antd"; import { AtSign, FileText, GripVertical, ImageIcon, Link2, Maximize2, Music2, Pencil, SlidersHorizontal, UserRound, Video, X } from "lucide-react"; import { type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; import { useState } from "react"; import { CanvasNodeType } from "@/types/canvas"; import type { CanvasTheme } from "./canvas-node-prompt-panel"; export function ReferenceToolsPopover({ canAutoMention, autoLinkEnabled, onAutoMention, onAutoLinkEnabledChange, accent, compact, }: { canAutoMention: boolean; autoLinkEnabled: boolean; onAutoMention: () => void; onAutoLinkEnabledChange: (enabled: boolean) => void; accent: string; compact: boolean; }) { return (
智能引用
输入素材序号或名称后按 Tab,可快速引用
AutoLink
} >
); } export function referenceShelfHeading(references: CanvasResourceReference[]) { const label = references.every((reference) => reference.kind === "image" || reference.kind === "character") ? "参考图" : "参考素材"; return `${label} · ${references.length}`; } export function ConnectedReferenceShelf({ targetNodeId, references, theme, onInsert, onRemove, onReorder, onReplaceReference, onReplaceReferenceFiles, }: { targetNodeId?: string; references: CanvasResourceReference[]; theme: CanvasTheme; onInsert: (reference: CanvasResourceReference) => void; onRemove?: (reference: CanvasResourceReference) => void; onReorder?: (orderedNodeIds: string[]) => void; onReplaceReference?: (oldReference: CanvasResourceReference, sourceNodeId: string) => void; onReplaceReferenceFiles?: (oldReference: CanvasResourceReference, files: File[]) => void; }) { const activeReferences = references.filter((item) => item.active && item.kind !== "skill" && item.kind !== "tool"); const [imagePreview, setImagePreview] = useState(null); const [draggedReferenceId, setDraggedReferenceId] = useState(null); const [dropTargetReferenceId, setDropTargetReferenceId] = useState(null); if (!activeReferences.length) return null; const moveReference = (sourceId: string, targetId: string) => { if (!onReorder || sourceId === targetId) return; const sourceIndex = activeReferences.findIndex((reference) => reference.nodeId === sourceId); const targetIndex = activeReferences.findIndex((reference) => reference.nodeId === targetId); if (sourceIndex < 0 || targetIndex < 0) return; const ordered = [...activeReferences]; const [moved] = ordered.splice(sourceIndex, 1); ordered.splice(targetIndex, 0, moved); onReorder(ordered.map((reference) => reference.nodeId)); }; const moveReferenceByOffset = (sourceId: string, offset: -1 | 1) => { const sourceIndex = activeReferences.findIndex((reference) => reference.nodeId === sourceId); const target = activeReferences[sourceIndex + offset]; if (!target) return; moveReference(sourceId, target.nodeId); }; return ( <>
{activeReferences.map((reference, index) => { const canPreview = Boolean(reference.previewUrl) && (reference.kind === "image" || reference.kind === "character" || reference.kind === "video"); const isDropTarget = dropTargetReferenceId === reference.id; return ( { if (draggedReferenceId) { if (!onReorder) return; event.preventDefault(); event.dataTransfer.dropEffect = "move"; return; } const hasImageNode = event.dataTransfer.types.includes("application/x-canvas-image-node-id"); const hasFiles = event.dataTransfer.types.includes("Files"); if (hasImageNode || hasFiles) { event.preventDefault(); event.stopPropagation(); event.dataTransfer.dropEffect = "copy"; if (dropTargetReferenceId !== reference.id) { setDropTargetReferenceId(reference.id); } } }} onDragLeave={(event) => { if (!event.currentTarget.contains(event.relatedTarget as Node)) { if (dropTargetReferenceId === reference.id) { setDropTargetReferenceId(null); } } }} onDrop={(event) => { if (dropTargetReferenceId === reference.id) { setDropTargetReferenceId(null); } if (draggedReferenceId) { event.preventDefault(); const sourceId = draggedReferenceId || event.dataTransfer.getData("text/plain"); setDraggedReferenceId(null); moveReference(sourceId, reference.nodeId); return; } const sourceNodeId = event.dataTransfer.getData("application/x-canvas-image-node-id"); if (sourceNodeId && sourceNodeId !== reference.nodeId && onReplaceReference) { event.preventDefault(); event.stopPropagation(); onReplaceReference(reference, sourceNodeId); return; } const files = Array.from(event.dataTransfer.files).filter((f) => f.type.startsWith("image/")); if (files.length && onReplaceReferenceFiles) { event.preventDefault(); event.stopPropagation(); onReplaceReferenceFiles(reference, files); return; } }} > {onReorder ? ( ) : null} {index + 1} {onRemove ? ( ) : null} ); })}
{imagePreview?.previewUrl ? ( !open && setImagePreview(null), }} /> ) : null} ); } export function ReferenceThumbnail({ reference }: { reference: CanvasResourceReference }) { if (reference.kind === "image" && reference.previewUrl) return ; if (reference.kind === "video" && reference.previewUrl) return ; if (reference.kind === "character" && reference.previewUrl) return ; const Icon = reference.sourceType === CanvasNodeType.Drawing ? Pencil : reference.kind === "character" ? UserRound : reference.kind === "audio" ? Music2 : reference.kind === "video" ? Video : reference.kind === "image" ? ImageIcon : FileText; return ( ); }