feat: implement batch upload operation and integrate with create operations

This commit is contained in:
yyh 2026-03-26 14:59:06 +08:00
parent ff63af495c
commit 6408300c35
No known key found for this signature in database
3 changed files with 149 additions and 14 deletions

View File

@ -0,0 +1,147 @@
'use client'
import type { BatchUploadNodeInput, BatchUploadNodeOutput } from '@/types/app-asset'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { consoleClient, consoleQuery } from '@/service/client'
import { uploadToPresignedUrl } from '@/service/upload-to-presigned-url'
import { useBatchUpload } from '@/service/use-app-asset'
type BatchUploadOperationVariables = {
appId: string
tree: BatchUploadNodeInput[]
files: Map<string, File>
parentId?: string | null
onProgress?: (uploaded: number, total: number) => void
}
type BatchUploadTask = {
file: File
url: string
}
const uploadBatchUploadTasks = async ({
tasks,
onProgress,
}: {
tasks: BatchUploadTask[]
onProgress?: (uploaded: number, total: number) => void
}) => {
let completed = 0
const total = tasks.length
await Promise.all(
tasks.map(async (task) => {
await uploadToPresignedUrl({
file: task.file,
uploadUrl: task.url,
})
completed++
onProgress?.(completed, total)
}),
)
}
const createBatchUploadTreeInParent = async ({
appId,
tree,
files,
parentId,
pathPrefix = '',
}: {
appId: string
tree: BatchUploadNodeInput[]
files: Map<string, File>
parentId: string
pathPrefix?: string
}): Promise<{ nodes: BatchUploadNodeOutput[], tasks: BatchUploadTask[] }> => {
const nodes: BatchUploadNodeOutput[] = []
const tasks: BatchUploadTask[] = []
for (const inputNode of tree) {
const sourcePath = pathPrefix ? `${pathPrefix}/${inputNode.name}` : inputNode.name
if (inputNode.node_type === 'folder') {
const folder = await consoleClient.appAsset.createFolder({
params: { appId },
body: { name: inputNode.name, parent_id: parentId },
})
const childrenResult = await createBatchUploadTreeInParent({
appId,
tree: inputNode.children ?? [],
files,
parentId: folder.id,
pathPrefix: sourcePath,
})
nodes.push({
id: folder.id,
name: folder.name,
node_type: folder.node_type,
size: folder.size,
children: childrenResult.nodes,
})
tasks.push(...childrenResult.tasks)
continue
}
const file = files.get(sourcePath)
if (!file)
throw new Error(`Missing file for batch upload path: ${sourcePath}`)
const { node, upload_url } = await consoleClient.appAsset.getFileUploadUrl({
params: { appId },
body: {
name: inputNode.name,
size: inputNode.size ?? file.size,
parent_id: parentId,
},
})
nodes.push({
id: node.id,
name: node.name,
node_type: node.node_type,
size: node.size,
children: [],
upload_url,
})
tasks.push({ file, url: upload_url })
}
return { nodes, tasks }
}
export function useBatchUploadOperation() {
const queryClient = useQueryClient()
const batchUpload = useBatchUpload()
return useMutation({
mutationKey: consoleQuery.appAsset.batchUpload.mutationKey(),
mutationFn: async (variables: BatchUploadOperationVariables): Promise<BatchUploadNodeOutput[]> => {
if (!variables.parentId)
return batchUpload.mutateAsync(variables)
try {
const result = await createBatchUploadTreeInParent({
appId: variables.appId,
tree: variables.tree,
files: variables.files,
parentId: variables.parentId,
})
await uploadBatchUploadTasks({
tasks: result.tasks,
onProgress: variables.onProgress,
})
return result.nodes
}
finally {
await queryClient.invalidateQueries({
queryKey: consoleQuery.appAsset.tree.key({ type: 'query', input: { params: { appId: variables.appId } } }),
})
}
},
})
}

View File

@ -5,12 +5,12 @@ import type { SkillEditorSliceShape } from '@/app/components/workflow/store/work
import type { BatchUploadNodeInput } from '@/types/app-asset'
import { useCallback, useRef } from 'react'
import {
useBatchUpload,
useCreateAppAssetFolder,
useUploadFileWithPresignedUrl,
} from '@/service/use-app-asset'
import { prepareSkillUploadFile } from '../../../utils/skill-upload-utils'
import { useSkillTreeUpdateEmitter } from '../data/use-skill-tree-collaboration'
import { useBatchUploadOperation } from './use-batch-upload-operation'
type UseCreateOperationsOptions = {
parentId: string | null
@ -34,7 +34,7 @@ export function useCreateOperations({
const { isPending: isCreateFolderPending } = useCreateAppAssetFolder()
const { mutateAsync: uploadFileAsync, isPending: isUploadFilePending } = useUploadFileWithPresignedUrl()
const { mutateAsync: batchUploadAsync, isPending: isBatchUploadPending } = useBatchUpload()
const { mutateAsync: batchUploadAsync, isPending: isBatchUploadPending } = useBatchUploadOperation()
const emitTreeUpdate = useSkillTreeUpdateEmitter()
const handleNewFile = useCallback(() => {

View File

@ -271,7 +271,6 @@ export const useBatchUpload = () => {
appId,
tree,
files,
parentId,
onProgress,
}: {
appId: string
@ -285,17 +284,6 @@ export const useBatchUpload = () => {
body: { children: tree },
})
if (parentId) {
await Promise.all(
response.children.map(node =>
consoleClient.appAsset.moveNode({
params: { appId, nodeId: node.id },
body: { parent_id: parentId },
}),
),
)
}
const uploadTasks: Array<{ path: string, file: File, url: string }> = []
const extractUploads = (nodes: BatchUploadNodeOutput[], pathPrefix: string = '') => {