mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-16 19:35:53 +08:00
file-uploader
This commit is contained in:
parent
d01e97c1fc
commit
6fdcf6ee21
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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}
|
||||
|
@ -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}>
|
||||
|
@ -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] || ''
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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] = {
|
||||
|
@ -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[]
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user