diff --git a/web/app/components/workflow/skill/sidebar-search-add.tsx b/web/app/components/workflow/skill/sidebar-search-add.tsx index 8559850671..128e4d065e 100644 --- a/web/app/components/workflow/skill/sidebar-search-add.tsx +++ b/web/app/components/workflow/skill/sidebar-search-add.tsx @@ -1,92 +1,82 @@ 'use client' import type { FC } from 'react' -import { RiAddLine, RiFile3Line, RiFolderAddLine } from '@remixicon/react' +import { + RiAddLine, + RiFileAddLine, + RiFolderAddLine, + RiFolderUploadLine, + RiUploadLine, +} from '@remixicon/react' import * as React from 'react' -import { useCallback, useRef, useState } from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' import SearchInput from '@/app/components/base/search-input' -import Toast from '@/app/components/base/toast' -import { useCreateAppAssetFile, useCreateAppAssetFolder } from '@/service/use-app-asset' +import { useGetAppAssetTree } from '@/service/use-app-asset' import { cn } from '@/utils/classnames' +import { useFileOperations } from './hooks/use-file-operations' +import { useSkillEditorStore } from './store' +import { getTargetFolderIdFromSelection } from './utils/tree-utils' + +type MenuItemProps = { + icon: React.ElementType + label: string + onClick: () => void + disabled?: boolean +} + +const MenuItem: React.FC = ({ icon: Icon, label, onClick, disabled }) => ( + +) const SidebarSearchAdd: FC = () => { const { t } = useTranslation('workflow') const [searchValue, setSearchValue] = useState('') const [showMenu, setShowMenu] = useState(false) - const fileInputRef = useRef(null) const appDetail = useAppStore(s => s.appDetail) const appId = appDetail?.id || '' + const { data: treeData } = useGetAppAssetTree(appId) + const activeTabId = useSkillEditorStore(s => s.activeTabId) - const createFolder = useCreateAppAssetFolder() - const createFile = useCreateAppAssetFile() + const targetFolderId = useMemo(() => { + if (!treeData?.children) + return 'root' + return getTargetFolderIdFromSelection(activeTabId, treeData.children) + }, [activeTabId, treeData?.children]) - const handleNewFolder = useCallback(async () => { - setShowMenu(false) - if (!appId) - return - - const timestamp = Date.now() - const folderName = `${t('skillSidebar.newFolder')}-${timestamp}` - - try { - await createFolder.mutateAsync({ - appId, - payload: { - name: folderName, - parent_id: null, - }, - }) - Toast.notify({ - type: 'success', - message: t('skillSidebar.addFolder'), - }) - } - catch (error) { - Toast.notify({ - type: 'error', - message: String(error), - }) - } - }, [appId, createFolder, t]) - - const handleUploadClick = useCallback(() => { - setShowMenu(false) - fileInputRef.current?.click() - }, []) - - const handleFileChange = useCallback(async (e: React.ChangeEvent) => { - const files = e.target.files - if (!files || files.length === 0 || !appId) - return - - const file = files[0] - - try { - await createFile.mutateAsync({ - appId, - name: file.name, - file, - parentId: null, - }) - Toast.notify({ - type: 'success', - message: t('skillSidebar.addFile'), - }) - } - catch (error) { - Toast.notify({ - type: 'error', - message: String(error), - }) - } - - e.target.value = '' - }, [appId, createFile, t]) + const { + fileInputRef, + folderInputRef, + isLoading, + handleNewFile, + handleNewFolder, + handleFileChange, + handleFolderChange, + } = useFileOperations({ + nodeId: targetFolderId, + onClose: () => setShowMenu(false), + }) return (
@@ -113,35 +103,53 @@ const SidebarSearchAdd: FC = () => { -
-
+ + + + + - - - {t('skillSidebar.addFolder')} - -
-
- - - {t('skillSidebar.addFile')} - -
+ disabled={isLoading} + /> + +
+ + fileInputRef.current?.click()} + disabled={isLoading} + /> + folderInputRef.current?.click()} + disabled={isLoading} + />
- -
) } diff --git a/web/app/components/workflow/skill/utils/tree-utils.ts b/web/app/components/workflow/skill/utils/tree-utils.ts index 43382ba302..d71787f2b3 100644 --- a/web/app/components/workflow/skill/utils/tree-utils.ts +++ b/web/app/components/workflow/skill/utils/tree-utils.ts @@ -84,3 +84,21 @@ export function getAllDescendantFileIds( return fileIds } + +export function getTargetFolderIdFromSelection( + selectedId: string | null, + nodes: AppAssetTreeView[], +): string { + if (!selectedId) + return 'root' + + const selectedNode = findNodeById(nodes, selectedId) + if (!selectedNode) + return 'root' + + if (selectedNode.node_type === 'folder') + return selectedNode.id + + const ancestors = getAncestorIds(selectedId, nodes) + return ancestors.length > 0 ? ancestors[ancestors.length - 1] : 'root' +}