mirror of
https://github.com/langgenius/dify.git
synced 2026-03-16 20:37:42 +08:00
- Replace FormData file upload with presigned URL two-step upload - Add batch-upload contract for folder uploads (reduces N+M to 1+M requests) - Remove deprecated createFile contract and useCreateAppAssetFile hook - Remove checksum field from AppAssetNode and AppAssetTreeView types - Add upload-to-presigned-url utility for direct storage uploads
111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
// Base drag-and-drop handler for file uploads
|
|
// Used by use-root-file-drop and use-folder-file-drop
|
|
|
|
import { useCallback } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
|
import Toast from '@/app/components/base/toast'
|
|
import { useWorkflowStore } from '@/app/components/workflow/store'
|
|
import { useUploadFileWithPresignedUrl } from '@/service/use-app-asset'
|
|
import { ROOT_ID } from '../constants'
|
|
|
|
type FileDropTarget = {
|
|
folderId: string | null
|
|
isFolder: boolean
|
|
}
|
|
|
|
export function useFileDrop() {
|
|
const { t } = useTranslation('workflow')
|
|
const appDetail = useAppStore(s => s.appDetail)
|
|
const appId = appDetail?.id || ''
|
|
const storeApi = useWorkflowStore()
|
|
const uploadFile = useUploadFileWithPresignedUrl()
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent, target: FileDropTarget) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
// Only handle file drops from the system (not internal tree drags)
|
|
if (!e.dataTransfer.types.includes('Files'))
|
|
return
|
|
|
|
e.dataTransfer.dropEffect = 'copy'
|
|
|
|
// Use ROOT_ID to indicate dragging over root (to distinguish from null = "not dragging")
|
|
storeApi.getState().setCurrentDragType('upload')
|
|
storeApi.getState().setDragOverFolderId(target.folderId ?? ROOT_ID)
|
|
}, [storeApi])
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
storeApi.getState().setCurrentDragType(null)
|
|
storeApi.getState().setDragOverFolderId(null)
|
|
}, [storeApi])
|
|
|
|
const handleDrop = useCallback(async (e: React.DragEvent, targetFolderId: string | null) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
storeApi.getState().setCurrentDragType(null)
|
|
storeApi.getState().setDragOverFolderId(null)
|
|
|
|
// Get files from dataTransfer, filter out directories (which have no type)
|
|
const items = Array.from(e.dataTransfer.items || [])
|
|
const files: File[] = []
|
|
|
|
for (const item of items) {
|
|
if (item.kind === 'file') {
|
|
const entry = item.webkitGetAsEntry?.()
|
|
// Skip directories - they have isDirectory = true
|
|
if (entry?.isDirectory) {
|
|
Toast.notify({
|
|
type: 'error',
|
|
message: t('skillSidebar.menu.folderDropNotSupported'),
|
|
})
|
|
continue
|
|
}
|
|
const file = item.getAsFile()
|
|
if (file)
|
|
files.push(file)
|
|
}
|
|
}
|
|
|
|
if (files.length === 0)
|
|
return
|
|
|
|
try {
|
|
await Promise.all(
|
|
files.map(file =>
|
|
uploadFile.mutateAsync({
|
|
appId,
|
|
file,
|
|
parentId: targetFolderId,
|
|
}),
|
|
),
|
|
)
|
|
|
|
Toast.notify({
|
|
type: 'success',
|
|
message: t('skillSidebar.menu.filesUploaded', { count: files.length }),
|
|
})
|
|
}
|
|
catch {
|
|
Toast.notify({
|
|
type: 'error',
|
|
message: t('skillSidebar.menu.uploadError'),
|
|
})
|
|
}
|
|
}, [appId, uploadFile, t, storeApi])
|
|
|
|
return {
|
|
handleDragOver,
|
|
handleDragLeave,
|
|
handleDrop,
|
|
isUploading: uploadFile.isPending,
|
|
}
|
|
}
|