From 357489d444a5d286d07cf446ee8bf03c9974fa8a Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 20 Jan 2026 15:23:37 +0800 Subject: [PATCH] feat: multi select for file tree & clipboard support --- .../workflow/skill/file-tree/index.tsx | 13 ++-- .../workflow/skill/file-tree/menu-item.tsx | 7 +- .../workflow/skill/file-tree/node-menu.tsx | 61 +++++++++++++++++ .../workflow/skill/file-tree/tree-node.tsx | 4 +- .../skill/hooks/use-skill-shortcuts.ts | 66 +++++++++++++++++++ .../hooks/use-sync-tree-with-active-tab.ts | 4 +- .../skill/hooks/use-tree-node-handlers.ts | 20 +++--- .../workflow/skill-editor/clipboard-slice.ts | 38 +++++++++++ .../workflow/skill-editor/file-tree-slice.ts | 16 +++++ .../store/workflow/skill-editor/index.ts | 5 ++ .../store/workflow/skill-editor/types.ts | 20 ++++++ web/i18n/en-US/workflow.json | 3 + web/i18n/zh-Hans/workflow.json | 3 + 13 files changed, 236 insertions(+), 24 deletions(-) create mode 100644 web/app/components/workflow/skill/hooks/use-skill-shortcuts.ts create mode 100644 web/app/components/workflow/store/workflow/skill-editor/clipboard-slice.ts diff --git a/web/app/components/workflow/skill/file-tree/index.tsx b/web/app/components/workflow/skill/file-tree/index.tsx index 5e41e7802b..5e24ff19de 100644 --- a/web/app/components/workflow/skill/file-tree/index.tsx +++ b/web/app/components/workflow/skill/file-tree/index.tsx @@ -19,6 +19,7 @@ import { CONTEXT_MENU_TYPE, ROOT_ID } from '../constants' import { useInlineCreateNode } from '../hooks/use-inline-create-node' import { useRootFileDrop } from '../hooks/use-root-file-drop' import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree' +import { useSkillShortcuts } from '../hooks/use-skill-shortcuts' import { useSyncTreeWithActiveTab } from '../hooks/use-sync-tree-with-active-tab' import ArtifactsSection from './artifacts-section' import DragActionTooltip from './drag-action-tooltip' @@ -62,7 +63,6 @@ const FileTree: React.FC = ({ className }) => { const expandedFolderIds = useStore(s => s.expandedFolderIds) const activeTabId = useStore(s => s.activeTabId) - const selectedTreeNodeId = useStore(s => s.selectedTreeNodeId) const dragOverFolderId = useStore(s => s.dragOverFolderId) const searchTerm = useStore(s => s.fileTreeSearchTerm) const storeApi = useWorkflowStore() @@ -123,18 +123,16 @@ const FileTree: React.FC = ({ className }) => { }, [storeApi]) const handleSelect = useCallback((nodes: NodeApi[]) => { - const selectedId = nodes[0]?.id ?? null - storeApi.getState().setSelectedTreeNodeId(selectedId) + storeApi.getState().setSelectedNodeIds(nodes.map(n => n.id)) }, [storeApi]) - // Clicking blank area clears selection for root-level creation const handleBlankAreaClick = useCallback(() => { - storeApi.getState().setSelectedTreeNodeId(null) + storeApi.getState().clearSelection() }, [storeApi]) const handleBlankAreaContextMenu = useCallback((e: React.MouseEvent) => { e.preventDefault() - storeApi.getState().setSelectedTreeNodeId(null) + storeApi.getState().clearSelection() storeApi.getState().setContextMenu({ top: e.clientY, left: e.clientX, @@ -147,6 +145,8 @@ const FileTree: React.FC = ({ className }) => { activeTabId, }) + useSkillShortcuts({ treeRef }) + if (isLoading) { return (
@@ -239,7 +239,6 @@ const FileTree: React.FC = ({ className }) => { indent={20} overscanCount={5} openByDefault={false} - selection={selectedTreeNodeId ?? undefined} initialOpenState={initialOpensObject} onToggle={handleToggle} onSelect={handleSelect} diff --git a/web/app/components/workflow/skill/file-tree/menu-item.tsx b/web/app/components/workflow/skill/file-tree/menu-item.tsx index 3af2799292..d3458e41f3 100644 --- a/web/app/components/workflow/skill/file-tree/menu-item.tsx +++ b/web/app/components/workflow/skill/file-tree/menu-item.tsx @@ -4,6 +4,7 @@ import type { VariantProps } from 'class-variance-authority' import type { FC } from 'react' import { cva } from 'class-variance-authority' import * as React from 'react' +import ShortcutsName from '@/app/components/workflow/shortcuts-name' import { cn } from '@/utils/classnames' const menuItemVariants = cva( @@ -52,11 +53,12 @@ const labelVariants = cva('system-sm-regular text-text-secondary', { export type MenuItemProps = { icon: React.ElementType label: string + kbd?: string[] onClick: React.MouseEventHandler disabled?: boolean } & VariantProps -const MenuItem: FC = ({ icon: Icon, label, onClick, disabled, variant }) => { +const MenuItem: FC = ({ icon: Icon, label, kbd, onClick, disabled, variant }) => { const handleClick = React.useCallback((event: React.MouseEvent) => { event.stopPropagation() onClick(event) @@ -70,7 +72,8 @@ const MenuItem: FC = ({ icon: Icon, label, onClick, disabled, var className={cn(menuItemVariants({ variant }))} >