mirror of
https://github.com/langgenius/dify.git
synced 2026-03-01 19:22:10 -05:00
Wire up the "Create Blank Skill" action card to open a modal where users enter a skill name. The modal validates against existing skill names in real-time and creates a folder with a SKILL.md file via batchUpload, then opens the file as a pinned tab.
361 lines
9.9 KiB
TypeScript
361 lines
9.9 KiB
TypeScript
import type {
|
|
AppAssetNode,
|
|
AppAssetTreeResponse,
|
|
BatchUploadNodeInput,
|
|
BatchUploadNodeOutput,
|
|
CreateFolderPayload,
|
|
GetFileUploadUrlPayload,
|
|
MoveNodePayload,
|
|
RenameNodePayload,
|
|
ReorderNodePayload,
|
|
UpdateFileContentPayload,
|
|
} from '@/types/app-asset'
|
|
import {
|
|
useMutation,
|
|
useQuery,
|
|
useQueryClient,
|
|
} from '@tanstack/react-query'
|
|
import { consoleClient, consoleQuery } from '@/service/client'
|
|
import { upload } from './base'
|
|
import { uploadToPresignedUrl } from './upload-to-presigned-url'
|
|
|
|
type UseGetAppAssetTreeOptions<TData = AppAssetTreeResponse> = {
|
|
select?: (data: AppAssetTreeResponse) => TData
|
|
}
|
|
|
|
export function useGetAppAssetTree<TData = AppAssetTreeResponse>(
|
|
appId: string,
|
|
options?: UseGetAppAssetTreeOptions<TData>,
|
|
) {
|
|
return useQuery({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId } } }),
|
|
queryFn: () => consoleClient.appAsset.tree({ params: { appId } }),
|
|
enabled: !!appId,
|
|
select: options?.select,
|
|
})
|
|
}
|
|
|
|
export const useCreateAppAssetFolder = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.createFolder.mutationKey(),
|
|
mutationFn: ({ appId, payload }: { appId: string, payload: CreateFolderPayload }) => {
|
|
return consoleClient.appAsset.createFolder({
|
|
params: { appId },
|
|
body: payload,
|
|
})
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useGetAppAssetFileContent = (appId: string, nodeId: string, options?: { enabled?: boolean }) => {
|
|
return useQuery({
|
|
queryKey: consoleQuery.appAsset.getFileContent.queryKey({ input: { params: { appId, nodeId } } }),
|
|
queryFn: () => consoleClient.appAsset.getFileContent({ params: { appId, nodeId } }),
|
|
select: (data) => {
|
|
try {
|
|
const result = JSON.parse(data.content)
|
|
return result
|
|
}
|
|
catch {
|
|
return { content: data.content }
|
|
}
|
|
},
|
|
enabled: (options?.enabled ?? true) && !!appId && !!nodeId,
|
|
})
|
|
}
|
|
|
|
export const useGetAppAssetFileDownloadUrl = (appId: string, nodeId: string, options?: { enabled?: boolean }) => {
|
|
return useQuery({
|
|
queryKey: consoleQuery.appAsset.getFileDownloadUrl.queryKey({ input: { params: { appId, nodeId } } }),
|
|
queryFn: () => consoleClient.appAsset.getFileDownloadUrl({ params: { appId, nodeId } }),
|
|
enabled: (options?.enabled ?? true) && !!appId && !!nodeId,
|
|
})
|
|
}
|
|
|
|
export const useUpdateAppAssetFileContent = () => {
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.updateFileContent.mutationKey(),
|
|
mutationFn: ({
|
|
appId,
|
|
nodeId,
|
|
payload,
|
|
}: {
|
|
appId: string
|
|
nodeId: string
|
|
payload: UpdateFileContentPayload
|
|
}) => {
|
|
return consoleClient.appAsset.updateFileContent({
|
|
params: { appId, nodeId },
|
|
body: { content: JSON.stringify(payload) },
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useUpdateAppAssetFileByUpload = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
appId,
|
|
nodeId,
|
|
file,
|
|
onProgress,
|
|
}: {
|
|
appId: string
|
|
nodeId: string
|
|
file: File
|
|
onProgress?: (progress: number) => void
|
|
}): Promise<AppAssetNode> => {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
|
|
const xhr = new XMLHttpRequest()
|
|
return upload(
|
|
{
|
|
xhr,
|
|
method: 'PUT',
|
|
data: formData,
|
|
onprogress: onProgress
|
|
? (e) => {
|
|
if (e.lengthComputable)
|
|
onProgress(Math.round((e.loaded / e.total) * 100))
|
|
}
|
|
: undefined,
|
|
},
|
|
false,
|
|
`/apps/${appId}/assets/files/${nodeId}`,
|
|
) as Promise<AppAssetNode>
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.getFileContent.queryKey({
|
|
input: { params: { appId: variables.appId, nodeId: variables.nodeId } },
|
|
}),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useDeleteAppAssetNode = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.deleteNode.mutationKey(),
|
|
mutationFn: ({ appId, nodeId }: { appId: string, nodeId: string }) => {
|
|
return consoleClient.appAsset.deleteNode({
|
|
params: { appId, nodeId },
|
|
})
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useRenameAppAssetNode = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.renameNode.mutationKey(),
|
|
mutationFn: ({
|
|
appId,
|
|
nodeId,
|
|
payload,
|
|
}: {
|
|
appId: string
|
|
nodeId: string
|
|
payload: RenameNodePayload
|
|
}) => {
|
|
return consoleClient.appAsset.renameNode({
|
|
params: { appId, nodeId },
|
|
body: payload,
|
|
})
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useMoveAppAssetNode = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.moveNode.mutationKey(),
|
|
mutationFn: ({
|
|
appId,
|
|
nodeId,
|
|
payload,
|
|
}: {
|
|
appId: string
|
|
nodeId: string
|
|
payload: MoveNodePayload
|
|
}) => {
|
|
return consoleClient.appAsset.moveNode({
|
|
params: { appId, nodeId },
|
|
body: payload,
|
|
})
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useReorderAppAssetNode = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.reorderNode.mutationKey(),
|
|
mutationFn: ({
|
|
appId,
|
|
nodeId,
|
|
payload,
|
|
}: {
|
|
appId: string
|
|
nodeId: string
|
|
payload: ReorderNodePayload
|
|
}) => {
|
|
return consoleClient.appAsset.reorderNode({
|
|
params: { appId, nodeId },
|
|
body: payload,
|
|
})
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const usePublishAppAssets = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.publish.mutationKey(),
|
|
mutationFn: (appId: string) => {
|
|
return consoleClient.appAsset.publish({
|
|
params: { appId },
|
|
})
|
|
},
|
|
onSuccess: (_, appId) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useUploadFileWithPresignedUrl = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.getFileUploadUrl.mutationKey(),
|
|
mutationFn: async ({
|
|
appId,
|
|
file,
|
|
parentId,
|
|
onProgress,
|
|
}: {
|
|
appId: string
|
|
file: File
|
|
parentId?: string | null
|
|
onProgress?: (progress: number) => void
|
|
}): Promise<AppAssetNode> => {
|
|
const payload: GetFileUploadUrlPayload = {
|
|
name: file.name,
|
|
size: file.size,
|
|
parent_id: parentId,
|
|
}
|
|
|
|
const { node, upload_url } = await consoleClient.appAsset.getFileUploadUrl({
|
|
params: { appId },
|
|
body: payload,
|
|
})
|
|
|
|
await uploadToPresignedUrl({
|
|
file,
|
|
uploadUrl: upload_url,
|
|
onProgress,
|
|
})
|
|
|
|
return node
|
|
},
|
|
onSettled: (_, __, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useBatchUpload = () => {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationKey: consoleQuery.appAsset.batchUpload.mutationKey(),
|
|
mutationFn: async ({
|
|
appId,
|
|
tree,
|
|
files,
|
|
parentId,
|
|
onProgress,
|
|
}: {
|
|
appId: string
|
|
tree: BatchUploadNodeInput[]
|
|
files: Map<string, File>
|
|
parentId?: string | null
|
|
onProgress?: (uploaded: number, total: number) => void
|
|
}): Promise<BatchUploadNodeOutput[]> => {
|
|
const response = await consoleClient.appAsset.batchUpload({
|
|
params: { appId },
|
|
body: { children: tree, parent_id: parentId },
|
|
})
|
|
|
|
const uploadTasks: Array<{ path: string, file: File, url: string }> = []
|
|
|
|
const extractUploads = (nodes: BatchUploadNodeOutput[], pathPrefix: string = '') => {
|
|
for (const node of nodes) {
|
|
const currentPath = pathPrefix ? `${pathPrefix}/${node.name}` : node.name
|
|
if (node.upload_url) {
|
|
const file = files.get(currentPath)
|
|
if (file)
|
|
uploadTasks.push({ path: currentPath, file, url: node.upload_url })
|
|
}
|
|
if (node.children && node.children.length > 0)
|
|
extractUploads(node.children, currentPath)
|
|
}
|
|
}
|
|
|
|
extractUploads(response.children)
|
|
|
|
let completed = 0
|
|
const total = uploadTasks.length
|
|
|
|
await Promise.all(
|
|
uploadTasks.map(async (task) => {
|
|
await uploadToPresignedUrl({
|
|
file: task.file,
|
|
uploadUrl: task.url,
|
|
})
|
|
completed++
|
|
onProgress?.(completed, total)
|
|
}),
|
|
)
|
|
|
|
return response.children
|
|
},
|
|
onSettled: (_, __, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId: variables.appId } } }),
|
|
})
|
|
},
|
|
})
|
|
}
|