From 6fdcf6ee21c0234474823a526986d9849457eebd Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Wed, 25 Sep 2024 16:23:34 +0800 Subject: [PATCH] file-uploader --- .../base/chat/chat/answer/index.tsx | 11 ++++++++ web/app/components/base/chat/types.ts | 1 + .../file-uploader-in-attachment/file-item.tsx | 2 +- .../file-uploader-in-attachment/index.tsx | 7 ++++- .../file-uploader-in-chat-input/file-item.tsx | 3 ++- .../components/base/file-uploader/store.tsx | 11 +++++--- .../components/base/file-uploader/utils.ts | 17 ++++++++++++ .../hooks/use-check-start-node-form.ts | 26 ++++++++++++++++++- .../components/before-run-form/form-item.tsx | 7 ++--- .../panel/debug-and-preview/chat-wrapper.tsx | 9 ++++--- .../workflow/panel/debug-and-preview/hooks.ts | 8 +++++- web/types/workflow.ts | 12 +++++++++ 12 files changed, 100 insertions(+), 14 deletions(-) diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 4d1cc3559d..7274b23184 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -21,6 +21,7 @@ import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal import type { AppData } from '@/models/share' import AnswerIcon from '@/app/components/base/answer-icon' import cn from '@/utils/classnames' +import { FileList } from '@/app/components/base/file-uploader' type AnswerProps = { item: ChatItem @@ -56,6 +57,7 @@ const Answer: FC = ({ more, annotation, workflowProcess, + allFiles, } = item const hasAgentThoughts = !!agent_thoughts?.length @@ -153,6 +155,15 @@ const Answer: FC = ({ /> ) } + { + allFiles?.length && ( + + ) + } { annotation?.id && annotation.authorName && ( void diff --git a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx index 45d57267aa..be81c8ea4a 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx @@ -81,7 +81,7 @@ const FileInAttachmentItem = ({
{ - progress > 0 && progress < 100 && ( + progress >= 0 && !file.uploadedId && ( void fileConfig: FileUpload } const FileUploaderInAttachmentWrapper = ({ + value, onChange, fileConfig, }: FileUploaderInAttachmentWrapperProps) => { return ( - + ) diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx index f8c5b13474..d4b30e5ca4 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx @@ -76,13 +76,14 @@ const FileItem = ({ showDownloadAction && ( window.open(file.url, '_blank')} > ) } { - progress > 0 && progress < 100 && ( + progress >= 0 && !file.uploadedId && ( void } -export const createFileStore = (onChange?: (files: FileEntity[]) => void) => { +export const createFileStore = ( + value: FileEntity[] = [], + onChange?: (files: FileEntity[]) => void, +) => { return create(set => ({ - files: [], + files: [...value], setFiles: (files) => { set({ files }) onChange?.(files) @@ -43,16 +46,18 @@ export const useFileStore = () => { type FileProviderProps = { children: React.ReactNode + value?: FileEntity[] onChange?: (files: FileEntity[]) => void } export const FileContextProvider = ({ children, + value, onChange, }: FileProviderProps) => { const storeRef = useRef() if (!storeRef.current) - storeRef.current = createFileStore(onChange) + storeRef.current = createFileStore(value, onChange) return ( diff --git a/web/app/components/base/file-uploader/utils.ts b/web/app/components/base/file-uploader/utils.ts index d2e3493ce2..f761f1026f 100644 --- a/web/app/components/base/file-uploader/utils.ts +++ b/web/app/components/base/file-uploader/utils.ts @@ -4,6 +4,7 @@ import type { FileEntity } from './types' import { upload } from '@/service/base' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { SupportUploadFileTypes } from '@/app/components/workflow/types' +import type { FileResponse } from '@/types/workflow' type FileUploadParams = { file: File @@ -116,6 +117,22 @@ export const getProcessedFiles = (files: FileEntity[]) => { })) } +export const getProcessedFilesFromResponse = (files: FileResponse[]) => { + return files.map((fileItem) => { + return { + id: fileItem.related_id, + name: fileItem.filename, + size: 0, + type: fileItem.mime_type, + progress: 100, + transferMethod: fileItem.transfer_method, + supportFileType: fileItem.type, + uploadedId: fileItem.related_id, + url: fileItem.url, + } + }) +} + export const getFileNameFromUrl = (url: string) => { const urlParts = url.split('/') return urlParts[urlParts.length - 1] || '' diff --git a/web/app/components/workflow/hooks/use-check-start-node-form.ts b/web/app/components/workflow/hooks/use-check-start-node-form.ts index e5ad9710ca..946f7a0f36 100644 --- a/web/app/components/workflow/hooks/use-check-start-node-form.ts +++ b/web/app/components/workflow/hooks/use-check-start-node-form.ts @@ -2,9 +2,13 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import { useWorkflowStore } from '@/app/components/workflow/store' -import { BlockEnum } from '@/app/components/workflow/types' +import { + BlockEnum, + InputVarType, +} from '@/app/components/workflow/types' import { useToastContext } from '@/app/components/base/toast' import type { InputVar } from '@/app/components/workflow/types' +import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' export const useCheckStartNodeForm = () => { const { t } = useTranslation() @@ -40,7 +44,27 @@ export const useCheckStartNodeForm = () => { return true }, [storeApi, workflowStore, notify, t]) + const getProcessedInputs = useCallback((inputs: Record) => { + const { getNodes } = storeApi.getState() + const nodes = getNodes() + const startNode = nodes.find(node => node.data.type === BlockEnum.Start) + const variables: InputVar[] = startNode?.data.variables || [] + + const processedInputs = { ...inputs } + + variables.forEach((variable) => { + if (variable.type === InputVarType.multiFiles) + processedInputs[variable.variable] = getProcessedFiles(inputs[variable.variable]) + + if (variable.type === InputVarType.singleFile) + processedInputs[variable.variable] = getProcessedFiles([inputs[variable.variable]])[0] + }) + + return processedInputs + }, [storeApi]) + return { checkStartNodeForm, + getProcessedInputs, } } diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index 0e93ea7286..14afa5428f 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -23,7 +23,6 @@ import { Line3 } from '@/app/components/base/icons/src/public/common' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' import cn from '@/utils/classnames' -import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' type Props = { payload: InputVar @@ -161,9 +160,10 @@ const FormItem: FC = ({ } {(type === InputVarType.singleFile) && ( { if (files.length) - onChange(getProcessedFiles(files)[0]) + onChange(files[0]) }} fileConfig={{ allowed_file_types: inStepRun ? [SupportUploadFileTypes.custom] : payload.allowed_file_types, @@ -175,7 +175,8 @@ const FormItem: FC = ({ )} {(type === InputVarType.multiFiles) && ( onChange(getProcessedFiles(files))} + value={value} + onChange={files => onChange(files)} fileConfig={{ allowed_file_types: inStepRun ? [SupportUploadFileTypes.custom] : payload.allowed_file_types, allowed_file_extensions: inStepRun ? [] : payload.allowed_file_extensions, diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index a8094ba0be..06d67a13c0 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -62,7 +62,10 @@ const ChatWrapper = forwardRef(({ } }, [features.opening, features.suggested, features.text2speech, features.speech2text, features.citation, features.moderation, features.file]) const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel) - const { checkStartNodeForm } = useCheckStartNodeForm() + const { + checkStartNodeForm, + getProcessedInputs, + } = useCheckStartNodeForm() const { conversationId, @@ -89,7 +92,7 @@ const ChatWrapper = forwardRef(({ { query, files, - inputs: workflowStore.getState().inputs, + inputs: getProcessedInputs(workflowStore.getState().inputs), conversation_id: conversationId, parent_message_id: last_answer?.id || getLastAnswer(chatListRef.current)?.id || null, }, @@ -97,7 +100,7 @@ const ChatWrapper = forwardRef(({ onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appDetail!.id, messageId, getAbortController), }, ) - }, [chatListRef, conversationId, handleSend, workflowStore, appDetail]) + }, [chatListRef, conversationId, handleSend, workflowStore, appDetail, getProcessedInputs]) const doRegenerate = useCallback((chatItem: ChatItem) => { const index = chatList.findIndex(item => item.id === chatItem.id) diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index cc0063e604..3a5a278ba0 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -6,6 +6,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { produce, setAutoFreeze } from 'immer' +import { uniqBy } from 'lodash-es' import { useWorkflowRun } from '../../hooks' import { NodeRunningStatus, WorkflowRunningStatus } from '../../types' import type { @@ -16,7 +17,10 @@ import type { import { useToastContext } from '@/app/components/base/toast' import { TransferMethod } from '@/types/app' import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel/utils' -import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' +import { + getProcessedFiles, + getProcessedFilesFromResponse, +} from '@/app/components/base/file-uploader/utils' import type { FileEntity } from '@/app/components/base/file-uploader/types' type GetAbortController = (abortController: AbortController) => void @@ -378,6 +382,8 @@ export const useChat = ( : {}), ...data, } as any + const processedFilesFromResponse = getProcessedFilesFromResponse(data.files || []) + responseItem.allFiles = uniqBy([...(responseItem.allFiles || []), ...(processedFilesFromResponse || [])], 'id') handleUpdateChatList(produce(chatListRef.current, (draft) => { const currentIndex = draft.findIndex(item => item.id === responseItem.id) draft[currentIndex] = { diff --git a/web/types/workflow.ts b/web/types/workflow.ts index dbf2b3e587..80698d2a7c 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -6,6 +6,7 @@ import type { EnvironmentVariable, Node, } from '@/app/components/workflow/types' +import type { TransferMethod } from '@/types/app' export type NodeTracing = { id: string @@ -128,6 +129,16 @@ export type NodeStartedResponse = { } } +export type FileResponse = { + related_id: string + extension: string + filename: string + mime_type: string + transfer_method: TransferMethod + type: string + url: string +} + export type NodeFinishedResponse = { task_id: string workflow_run_id: string @@ -155,6 +166,7 @@ export type NodeFinishedResponse = { iteration_id?: string } created_at: number + files?: FileResponse[] } }