From fc83e2b1c4f8843840d6b29d5555d4659a89aff6 Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 20 Jan 2026 13:16:27 +0800 Subject: [PATCH] feat!: file download in skill file tree menu --- .../workflow/skill/file-tree/node-menu.tsx | 14 +++++ .../skill/hooks/use-download-operation.ts | 56 +++++++++++++++++++ .../skill/hooks/use-file-operations.ts | 13 ++++- web/i18n/en-US/workflow.json | 4 ++ web/i18n/zh-Hans/workflow.json | 4 ++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 web/app/components/workflow/skill/hooks/use-download-operation.ts diff --git a/web/app/components/workflow/skill/file-tree/node-menu.tsx b/web/app/components/workflow/skill/file-tree/node-menu.tsx index c9642490ac..786e9b34ef 100644 --- a/web/app/components/workflow/skill/file-tree/node-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/node-menu.tsx @@ -15,6 +15,7 @@ import { import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' +import { Download02 } from '@/app/components/base/icons/src/vender/solid/general' import { cn } from '@/utils/classnames' import { NODE_MENU_TYPE } from '../constants' import { useFileOperations } from '../hooks/use-file-operations' @@ -52,6 +53,7 @@ const NodeMenu: FC = ({ showDeleteConfirm, isLoading, isDeleting, + handleDownload, handleNewFile, handleNewFolder, handleFileChange, @@ -126,6 +128,18 @@ const NodeMenu: FC = ({ )} + {!isFolder && ( + <> + +
+ + )} + {showRenameDelete && ( <> void +} + +export function useDownloadOperation({ + appId, + nodeId, + onClose, +}: UseDownloadOperationOptions) { + const { t } = useTranslation('workflow') + const [isDownloading, setIsDownloading] = useState(false) + + const handleDownload = useCallback(async () => { + if (!nodeId || !appId) + return + + // Close menu immediately before any async operation + onClose() + + setIsDownloading(true) + try { + const { download_url } = await consoleClient.appAsset.getFileDownloadUrl({ + params: { appId, nodeId }, + }) + + // Open download URL in new tab (consistent with UnsupportedFileDownload) + if (typeof window !== 'undefined') + window.open(download_url, '_blank', 'noopener,noreferrer') + } + catch { + Toast.notify({ + type: 'error', + message: t('skillSidebar.menu.downloadError'), + }) + } + finally { + setIsDownloading(false) + } + }, [appId, nodeId, onClose, t]) + + return { + handleDownload, + isDownloading, + } +} diff --git a/web/app/components/workflow/skill/hooks/use-file-operations.ts b/web/app/components/workflow/skill/hooks/use-file-operations.ts index 78e417e18f..6da35b9470 100644 --- a/web/app/components/workflow/skill/hooks/use-file-operations.ts +++ b/web/app/components/workflow/skill/hooks/use-file-operations.ts @@ -9,6 +9,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import { useWorkflowStore } from '@/app/components/workflow/store' import { toApiParentId } from '../utils/tree-utils' import { useCreateOperations } from './use-create-operations' +import { useDownloadOperation } from './use-download-operation' import { useModifyOperations } from './use-modify-operations' import { useSkillAssetTreeData } from './use-skill-asset-tree' @@ -51,6 +52,12 @@ export function useFileOperations({ onClose, }) + const downloadOps = useDownloadOperation({ + appId, + nodeId, + onClose, + }) + return { // Create operations fileInputRef: createOps.fileInputRef, @@ -67,8 +74,12 @@ export function useFileOperations({ handleDeleteConfirm: modifyOps.handleDeleteConfirm, handleDeleteCancel: modifyOps.handleDeleteCancel, + // Download operation + handleDownload: downloadOps.handleDownload, + // Combined loading states - isLoading: createOps.isCreating || modifyOps.isDeleting, + isLoading: createOps.isCreating || modifyOps.isDeleting || downloadOps.isDownloading, isDeleting: modifyOps.isDeleting, + isDownloading: downloadOps.isDownloading, } } diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index b7af41b534..5a10ff7b41 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -1027,6 +1027,8 @@ "skillSidebar.menu.deleteConfirmTitle": "Delete folder?", "skillSidebar.menu.deleteError": "Failed to delete folder", "skillSidebar.menu.deleted": "Folder deleted successfully", + "skillSidebar.menu.download": "Download", + "skillSidebar.menu.downloadError": "Failed to download file", "skillSidebar.menu.fileCreated": "File created successfully", "skillSidebar.menu.fileDeleteConfirmContent": "This will permanently delete the file. If the file is open, its tab will be closed.", "skillSidebar.menu.fileDeleteConfirmTitle": "Delete file?", @@ -1048,6 +1050,8 @@ "skillSidebar.menu.uploadFile": "Upload File", "skillSidebar.menu.uploadFolder": "Upload Folder", "skillSidebar.newFolder": "New folder", + "skillSidebar.resetFilter": "Reset filter", + "skillSidebar.searchNoResults": "No file were found", "skillSidebar.searchPlaceholder": "Search files…", "skillSidebar.toggleFolder": "Toggle folder", "skillSidebar.unsavedChanges.confirmClose": "Discard", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 512c9c765e..27e8aac942 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -1019,6 +1019,8 @@ "skillSidebar.menu.deleteConfirmTitle": "删除文件夹?", "skillSidebar.menu.deleteError": "删除文件夹失败", "skillSidebar.menu.deleted": "文件夹删除成功", + "skillSidebar.menu.download": "下载", + "skillSidebar.menu.downloadError": "下载文件失败", "skillSidebar.menu.fileCreated": "文件创建成功", "skillSidebar.menu.fileDeleteConfirmContent": "这将永久删除该文件。如果文件已打开,其标签将被关闭。", "skillSidebar.menu.fileDeleteConfirmTitle": "删除文件?", @@ -1039,6 +1041,8 @@ "skillSidebar.menu.uploadFile": "上传文件", "skillSidebar.menu.uploadFolder": "上传文件夹", "skillSidebar.newFolder": "新建文件夹", + "skillSidebar.resetFilter": "重置筛选", + "skillSidebar.searchNoResults": "未找到文件", "skillSidebar.searchPlaceholder": "搜索文件...", "skillSidebar.unsavedChanges.confirmClose": "放弃", "skillSidebar.unsavedChanges.content": "您有未保存的更改,是否放弃?",