file-uploader

This commit is contained in:
StyleZhang 2024-09-25 16:23:34 +08:00
parent d01e97c1fc
commit 6fdcf6ee21
12 changed files with 100 additions and 14 deletions

View File

@ -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<AnswerProps> = ({
more,
annotation,
workflowProcess,
allFiles,
} = item
const hasAgentThoughts = !!agent_thoughts?.length
@ -153,6 +155,15 @@ const Answer: FC<AnswerProps> = ({
/>
)
}
{
allFiles?.length && (
<FileList
files={allFiles}
showDeleteAction={false}
showDownloadAction
/>
)
}
{
annotation?.id && annotation.authorName && (
<EditTitle

View File

@ -61,6 +61,7 @@ export type ChatItem = IChatItem & {
isError?: boolean
workflowProcess?: WorkflowProcess
conversationId?: string
allFiles?: FileEntity[]
}
export type OnSend = (message: string, files?: FileEntity[], last_answer?: ChatItem | null) => void

View File

@ -81,7 +81,7 @@ const FileInAttachmentItem = ({
</div>
<div className='shrink-0 flex items-center'>
{
progress > 0 && progress < 100 && (
progress >= 0 && !file.uploadedId && (
<ProgressCircle
className='mr-2.5'
percentage={progress}

View File

@ -111,15 +111,20 @@ const FileUploaderInAttachment = ({
}
type FileUploaderInAttachmentWrapperProps = {
value?: FileEntity[]
onChange: (files: FileEntity[]) => void
fileConfig: FileUpload
}
const FileUploaderInAttachmentWrapper = ({
value,
onChange,
fileConfig,
}: FileUploaderInAttachmentWrapperProps) => {
return (
<FileContextProvider onChange={onChange}>
<FileContextProvider
value={value}
onChange={onChange}
>
<FileUploaderInAttachment fileConfig={fileConfig} />
</FileContextProvider>
)

View File

@ -76,13 +76,14 @@ const FileItem = ({
showDownloadAction && (
<ActionButton
size='xs'
onClick={() => window.open(file.url, '_blank')}
>
<RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' />
</ActionButton>
)
}
{
progress > 0 && progress < 100 && (
progress >= 0 && !file.uploadedId && (
<ProgressCircle
percentage={progress}
size={12}

View File

@ -16,9 +16,12 @@ type Shape = {
setFiles: (files: FileEntity[]) => void
}
export const createFileStore = (onChange?: (files: FileEntity[]) => void) => {
export const createFileStore = (
value: FileEntity[] = [],
onChange?: (files: FileEntity[]) => void,
) => {
return create<Shape>(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<FileStore>()
if (!storeRef.current)
storeRef.current = createFileStore(onChange)
storeRef.current = createFileStore(value, onChange)
return (
<FileContext.Provider value={storeRef.current}>

View File

@ -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] || ''

View File

@ -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<string, any>) => {
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,
}
}

View File

@ -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<Props> = ({
}
{(type === InputVarType.singleFile) && (
<FileUploaderInAttachmentWrapper
value={value ? [value] : []}
onChange={(files) => {
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<Props> = ({
)}
{(type === InputVarType.multiFiles) && (
<FileUploaderInAttachmentWrapper
onChange={files => 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,

View File

@ -62,7 +62,10 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({
}
}, [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<ChatWrapperRefType, ChatWrapperProps>(({
{
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<ChatWrapperRefType, ChatWrapperProps>(({
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)

View File

@ -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] = {

View File

@ -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[]
}
}