| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import useSWR from 'swr' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import React, { useCallback, useEffect, useRef, useState } from 'react' | import React, { useCallback, useEffect, useRef, useState } from 'react' | ||||
| import produce, { setAutoFreeze } from 'immer' | import produce, { setAutoFreeze } from 'immer' | ||||
| import TextGeneration from '@/app/components/app/text-generate/item' | import TextGeneration from '@/app/components/app/text-generate/item' | ||||
| import { IS_CE_EDITION } from '@/config' | import { IS_CE_EDITION } from '@/config' | ||||
| import type { Inputs } from '@/models/debug' | import type { Inputs } from '@/models/debug' | ||||
| import { fetchFileUploadConfig } from '@/service/common' | |||||
| import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | ||||
| import { ModelFeatureEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | import { ModelFeatureEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | ||||
| import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' | import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' | ||||
| } = useContext(ConfigContext) | } = useContext(ConfigContext) | ||||
| const { eventEmitter } = useEventEmitterContextContext() | const { eventEmitter } = useEventEmitterContextContext() | ||||
| const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding) | const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding) | ||||
| const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setAutoFreeze(false) | setAutoFreeze(false) | ||||
| return () => { | return () => { | ||||
| visionConfig={{ | visionConfig={{ | ||||
| ...features.file! as VisionSettings, | ...features.file! as VisionSettings, | ||||
| transfer_methods: features.file!.allowed_file_upload_methods || [], | transfer_methods: features.file!.allowed_file_upload_methods || [], | ||||
| image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit, | |||||
| image_file_size_limit: features.file?.fileUploadConfig?.image_file_size_limit, | |||||
| }} | }} | ||||
| onVisionFilesChange={setCompletionFiles} | onVisionFilesChange={setCompletionFiles} | ||||
| /> | /> |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | ||||
| import useSWR from 'swr' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useContext } from 'use-context-selector' | import { useContext } from 'use-context-selector' | ||||
| import { usePathname } from 'next/navigation' | import { usePathname } from 'next/navigation' | ||||
| import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | ||||
| import { SupportUploadFileTypes } from '@/app/components/workflow/types' | import { SupportUploadFileTypes } from '@/app/components/workflow/types' | ||||
| import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' | import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' | ||||
| import { fetchFileUploadConfig } from '@/service/common' | |||||
| type PublishConfig = { | type PublishConfig = { | ||||
| modelConfig: ModelConfig | modelConfig: ModelConfig | ||||
| showAppConfigureFeaturesModal: state.showAppConfigureFeaturesModal, | showAppConfigureFeaturesModal: state.showAppConfigureFeaturesModal, | ||||
| setShowAppConfigureFeaturesModal: state.setShowAppConfigureFeaturesModal, | setShowAppConfigureFeaturesModal: state.setShowAppConfigureFeaturesModal, | ||||
| }))) | }))) | ||||
| const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) | |||||
| const latestPublishedAt = useMemo(() => appDetail?.model_config.updated_at, [appDetail]) | const latestPublishedAt = useMemo(() => appDetail?.model_config.updated_at, [appDetail]) | ||||
| const [formattingChanged, setFormattingChanged] = useState(false) | const [formattingChanged, setFormattingChanged] = useState(false) | ||||
| const { setShowAccountSettingModal } = useModalContext() | const { setShowAccountSettingModal } = useModalContext() | ||||
| allowed_file_extensions: modelConfig.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), | allowed_file_extensions: modelConfig.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), | ||||
| allowed_file_upload_methods: modelConfig.file_upload?.allowed_file_upload_methods || modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], | allowed_file_upload_methods: modelConfig.file_upload?.allowed_file_upload_methods || modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], | ||||
| number_limits: modelConfig.file_upload?.number_limits || modelConfig.file_upload?.image?.number_limits || 3, | number_limits: modelConfig.file_upload?.number_limits || modelConfig.file_upload?.image?.number_limits || 3, | ||||
| fileUploadConfig: fileUploadConfigResponse, | |||||
| } as FileUpload, | } as FileUpload, | ||||
| suggested: modelConfig.suggested_questions_after_answer || { enabled: false }, | suggested: modelConfig.suggested_questions_after_answer || { enabled: false }, | ||||
| citation: modelConfig.retriever_resource || { enabled: false }, | citation: modelConfig.retriever_resource || { enabled: false }, | ||||
| annotationReply: modelConfig.annotation_reply || { enabled: false }, | annotationReply: modelConfig.annotation_reply || { enabled: false }, | ||||
| } | } | ||||
| }, [modelConfig]) | |||||
| }, [fileUploadConfigResponse, modelConfig]) | |||||
| const handleFeaturesChange = useCallback((flag: any) => { | const handleFeaturesChange = useCallback((flag: any) => { | ||||
| setShowAppConfigureFeaturesModal(true) | setShowAppConfigureFeaturesModal(true) | ||||
| if (flag) | if (flag) | ||||
| }, | }, | ||||
| })) | })) | ||||
| const fileUpload = { ...features?.file } | |||||
| delete fileUpload?.fileUploadConfig | |||||
| // new model config data struct | // new model config data struct | ||||
| const data: BackendModelConfig = { | const data: BackendModelConfig = { | ||||
| // Simple Mode prompt | // Simple Mode prompt | ||||
| sensitive_word_avoidance: features?.moderation as any, | sensitive_word_avoidance: features?.moderation as any, | ||||
| speech_to_text: features?.speech2text as any, | speech_to_text: features?.speech2text as any, | ||||
| text_to_speech: features?.text2speech as any, | text_to_speech: features?.text2speech as any, | ||||
| file_upload: features?.file as any, | |||||
| file_upload: fileUpload as any, | |||||
| suggested_questions_after_answer: features?.suggested as any, | suggested_questions_after_answer: features?.suggested as any, | ||||
| retriever_resource: features?.citation as any, | retriever_resource: features?.citation as any, | ||||
| agent_mode: { | agent_mode: { |
| return { | return { | ||||
| ...config, | ...config, | ||||
| file_upload: { | |||||
| ...(config as any).file_upload, | |||||
| fileUploadConfig: (config as any).system_parameters, | |||||
| }, | |||||
| supportFeedback: true, | supportFeedback: true, | ||||
| opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, | opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, | ||||
| } as ChatConfig | } as ChatConfig |
| const Form = () => { | const Form = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { | const { | ||||
| appParams, | |||||
| inputsForms, | inputsForms, | ||||
| newConversationInputs, | newConversationInputs, | ||||
| newConversationInputsRef, | newConversationInputsRef, | ||||
| allowed_file_extensions: form.allowed_file_extensions, | allowed_file_extensions: form.allowed_file_extensions, | ||||
| allowed_file_upload_methods: form.allowed_file_upload_methods, | allowed_file_upload_methods: form.allowed_file_upload_methods, | ||||
| number_limits: 1, | number_limits: 1, | ||||
| fileUploadConfig: (appParams as any).system_parameters, | |||||
| }} | }} | ||||
| /> | /> | ||||
| ) | ) | ||||
| allowed_file_extensions: form.allowed_file_extensions, | allowed_file_extensions: form.allowed_file_extensions, | ||||
| allowed_file_upload_methods: form.allowed_file_upload_methods, | allowed_file_upload_methods: form.allowed_file_upload_methods, | ||||
| number_limits: form.max_length, | number_limits: form.max_length, | ||||
| fileUploadConfig: (appParams as any).system_parameters, | |||||
| }} | }} | ||||
| /> | /> | ||||
| ) | ) |
| return { | return { | ||||
| ...config, | ...config, | ||||
| file_upload: { | |||||
| ...(config as any).file_upload, | |||||
| fileUploadConfig: (config as any).system_parameters, | |||||
| }, | |||||
| supportFeedback: true, | supportFeedback: true, | ||||
| opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, | opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, | ||||
| } as ChatConfig | } as ChatConfig |
| const Form = () => { | const Form = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { | const { | ||||
| appParams, | |||||
| inputsForms, | inputsForms, | ||||
| newConversationInputs, | newConversationInputs, | ||||
| newConversationInputsRef, | newConversationInputsRef, | ||||
| allowed_file_extensions: form.allowed_file_extensions, | allowed_file_extensions: form.allowed_file_extensions, | ||||
| allowed_file_upload_methods: form.allowed_file_upload_methods, | allowed_file_upload_methods: form.allowed_file_upload_methods, | ||||
| number_limits: 1, | number_limits: 1, | ||||
| fileUploadConfig: (appParams as any).system_parameters, | |||||
| }} | }} | ||||
| /> | /> | ||||
| ) | ) | ||||
| allowed_file_extensions: form.allowed_file_extensions, | allowed_file_extensions: form.allowed_file_extensions, | ||||
| allowed_file_upload_methods: form.allowed_file_upload_methods, | allowed_file_upload_methods: form.allowed_file_upload_methods, | ||||
| number_limits: form.max_length, | number_limits: form.max_length, | ||||
| fileUploadConfig: (appParams as any).system_parameters, | |||||
| }} | }} | ||||
| /> | /> | ||||
| ) | ) |
| import type { Resolution, TransferMethod, TtsAutoPlay } from '@/types/app' | import type { Resolution, TransferMethod, TtsAutoPlay } from '@/types/app' | ||||
| import type { FileUploadConfigResponse } from '@/models/common' | |||||
| export type EnabledOrDisabled = { | export type EnabledOrDisabled = { | ||||
| enabled?: boolean | enabled?: boolean | ||||
| allowed_file_extensions?: string[] | allowed_file_extensions?: string[] | ||||
| allowed_file_upload_methods?: TransferMethod[] | allowed_file_upload_methods?: TransferMethod[] | ||||
| number_limits?: number | number_limits?: number | ||||
| fileUploadConfig?: FileUploadConfigResponse | |||||
| } & EnabledOrDisabled | } & EnabledOrDisabled | ||||
| export type AnnotationReplyConfig = { | export type AnnotationReplyConfig = { |
| // fallback for file size limit of dify_config | |||||
| export const IMG_SIZE_LIMIT = 10 * 1024 * 1024 | |||||
| export const FILE_SIZE_LIMIT = 15 * 1024 * 1024 | export const FILE_SIZE_LIMIT = 15 * 1024 * 1024 | ||||
| export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024 | |||||
| export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024 | |||||
| export const FILE_URL_REGEX = /^(https?|ftp):\/\// | export const FILE_URL_REGEX = /^(https?|ftp):\/\// |
| getSupportFileType, | getSupportFileType, | ||||
| isAllowedFileExtension, | isAllowedFileExtension, | ||||
| } from './utils' | } from './utils' | ||||
| import { FILE_SIZE_LIMIT } from './constants' | |||||
| import { | |||||
| AUDIO_SIZE_LIMIT, | |||||
| FILE_SIZE_LIMIT, | |||||
| IMG_SIZE_LIMIT, | |||||
| VIDEO_SIZE_LIMIT, | |||||
| } from '@/app/components/base/file-uploader/constants' | |||||
| import { useToastContext } from '@/app/components/base/toast' | import { useToastContext } from '@/app/components/base/toast' | ||||
| import { TransferMethod } from '@/types/app' | import { TransferMethod } from '@/types/app' | ||||
| import { SupportUploadFileTypes } from '@/app/components/workflow/types' | import { SupportUploadFileTypes } from '@/app/components/workflow/types' | ||||
| import type { FileUpload } from '@/app/components/base/features/types' | import type { FileUpload } from '@/app/components/base/features/types' | ||||
| import { formatFileSize } from '@/utils/format' | import { formatFileSize } from '@/utils/format' | ||||
| import { fetchRemoteFileInfo } from '@/service/common' | import { fetchRemoteFileInfo } from '@/service/common' | ||||
| import type { FileUploadConfigResponse } from '@/models/common' | |||||
| export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) => { | |||||
| const imgSizeLimit = Number(fileUploadConfig?.image_file_size_limit) * 1024 * 1024 || IMG_SIZE_LIMIT | |||||
| const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT | |||||
| const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT | |||||
| const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT | |||||
| return { | |||||
| imgSizeLimit, | |||||
| docSizeLimit, | |||||
| audioSizeLimit, | |||||
| videoSizeLimit, | |||||
| } | |||||
| } | |||||
| export const useFile = (fileConfig: FileUpload) => { | export const useFile = (fileConfig: FileUpload) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { notify } = useToastContext() | const { notify } = useToastContext() | ||||
| const fileStore = useFileStore() | const fileStore = useFileStore() | ||||
| const params = useParams() | const params = useParams() | ||||
| const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileConfig.fileUploadConfig) | |||||
| const checkSizeLimit = (fileType: string, fileSize: number) => { | |||||
| switch (fileType) { | |||||
| case SupportUploadFileTypes.image: { | |||||
| if (fileSize > imgSizeLimit) { | |||||
| notify({ | |||||
| type: 'error', | |||||
| message: t('common.fileUploader.uploadFromComputerLimit', { | |||||
| type: SupportUploadFileTypes.image, | |||||
| size: formatFileSize(imgSizeLimit), | |||||
| }), | |||||
| }) | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| case SupportUploadFileTypes.document: { | |||||
| if (fileSize > docSizeLimit) { | |||||
| notify({ | |||||
| type: 'error', | |||||
| message: t('common.fileUploader.uploadFromComputerLimit', { | |||||
| type: SupportUploadFileTypes.document, | |||||
| size: formatFileSize(docSizeLimit), | |||||
| }), | |||||
| }) | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| case SupportUploadFileTypes.audio: { | |||||
| if (fileSize > audioSizeLimit) { | |||||
| notify({ | |||||
| type: 'error', | |||||
| message: t('common.fileUploader.uploadFromComputerLimit', { | |||||
| type: SupportUploadFileTypes.audio, | |||||
| size: formatFileSize(audioSizeLimit), | |||||
| }), | |||||
| }) | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| case SupportUploadFileTypes.video: { | |||||
| if (fileSize > videoSizeLimit) { | |||||
| notify({ | |||||
| type: 'error', | |||||
| message: t('common.fileUploader.uploadFromComputerLimit', { | |||||
| type: SupportUploadFileTypes.video, | |||||
| size: formatFileSize(videoSizeLimit), | |||||
| }), | |||||
| }) | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| case SupportUploadFileTypes.custom: { | |||||
| if (fileSize > docSizeLimit) { | |||||
| notify({ | |||||
| type: 'error', | |||||
| message: t('common.fileUploader.uploadFromComputerLimit', { | |||||
| type: SupportUploadFileTypes.document, | |||||
| size: formatFileSize(docSizeLimit), | |||||
| }), | |||||
| }) | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| default: { | |||||
| return true | |||||
| } | |||||
| } | |||||
| } | |||||
| const handleAddFile = useCallback((newFile: FileEntity) => { | const handleAddFile = useCallback((newFile: FileEntity) => { | ||||
| const { | const { | ||||
| progress: 100, | progress: 100, | ||||
| supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)), | supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)), | ||||
| } | } | ||||
| handleUpdateFile(newFile) | |||||
| if (!checkSizeLimit(newFile.supportFileType, newFile.size)) | |||||
| handleRemoveFile(uploadingFile.id) | |||||
| else | |||||
| handleUpdateFile(newFile) | |||||
| }).catch(() => { | }).catch(() => { | ||||
| notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') }) | notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') }) | ||||
| handleRemoveFile(uploadingFile.id) | handleRemoveFile(uploadingFile.id) | ||||
| }) | }) | ||||
| }, [handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types]) | |||||
| }, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types]) | |||||
| const handleLoadFileFromLinkSuccess = useCallback(() => { }, []) | const handleLoadFileFromLinkSuccess = useCallback(() => { }, []) | ||||
| notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) | notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) | ||||
| return | return | ||||
| } | } | ||||
| if (file.size > FILE_SIZE_LIMIT) { | |||||
| notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerLimit', { size: formatFileSize(FILE_SIZE_LIMIT) }) }) | |||||
| const allowedFileTypes = fileConfig.allowed_file_types | |||||
| const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)) | |||||
| if (!checkSizeLimit(fileType, file.size)) | |||||
| return | return | ||||
| } | |||||
| const reader = new FileReader() | const reader = new FileReader() | ||||
| const isImage = file.type.startsWith('image') | const isImage = file.type.startsWith('image') | ||||
| const allowedFileTypes = fileConfig.allowed_file_types | |||||
| reader.addEventListener( | reader.addEventListener( | ||||
| 'load', | 'load', | ||||
| false, | false, | ||||
| ) | ) | ||||
| reader.readAsDataURL(file) | reader.readAsDataURL(file) | ||||
| }, [notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions]) | |||||
| }, [checkSizeLimit, notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions]) | |||||
| const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => { | const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => { | ||||
| const file = e.clipboardData?.files[0] | const file = e.clipboardData?.files[0] |
| setVisionConfig({ | setVisionConfig({ | ||||
| ...file_upload.image, | ...file_upload.image, | ||||
| image_file_size_limit: appParams?.system_parameters?.image_file_size_limit, | image_file_size_limit: appParams?.system_parameters?.image_file_size_limit, | ||||
| fileUploadConfig: appParams?.system_parameters, | |||||
| }) | }) | ||||
| const prompt_variables = userInputsFormToPromptVariables(user_input_form) | const prompt_variables = userInputsFormToPromptVariables(user_input_form) | ||||
| setPromptConfig({ | setPromptConfig({ |
| {item.type === 'file' && ( | {item.type === 'file' && ( | ||||
| <FileUploaderInAttachmentWrapper | <FileUploaderInAttachmentWrapper | ||||
| onChange={(files) => { onInputsChange({ ...inputs, [item.key]: getProcessedFiles(files)[0] }) }} | onChange={(files) => { onInputsChange({ ...inputs, [item.key]: getProcessedFiles(files)[0] }) }} | ||||
| fileConfig={item.config as any} | |||||
| fileConfig={{ | |||||
| ...item.config, | |||||
| fileUploadConfig: (visionConfig as any).fileUploadConfig, | |||||
| }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| {item.type === 'file-list' && ( | {item.type === 'file-list' && ( | ||||
| <FileUploaderInAttachmentWrapper | <FileUploaderInAttachmentWrapper | ||||
| onChange={(files) => { onInputsChange({ ...inputs, [item.key]: getProcessedFiles(files) }) }} | onChange={(files) => { onInputsChange({ ...inputs, [item.key]: getProcessedFiles(files) }) }} | ||||
| fileConfig={item.config as any} | |||||
| fileConfig={{ | |||||
| ...item.config, | |||||
| fileUploadConfig: (visionConfig as any).fileUploadConfig, | |||||
| }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| </div> | </div> |
| useRef, | useRef, | ||||
| useState, | useState, | ||||
| } from 'react' | } from 'react' | ||||
| import useSWR from 'swr' | |||||
| import { setAutoFreeze } from 'immer' | import { setAutoFreeze } from 'immer' | ||||
| import { | import { | ||||
| useEventListener, | useEventListener, | ||||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | import { useEventEmitterContextContext } from '@/context/event-emitter' | ||||
| import Confirm from '@/app/components/base/confirm' | import Confirm from '@/app/components/base/confirm' | ||||
| import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | ||||
| import { fetchFileUploadConfig } from '@/service/common' | |||||
| const nodeTypes = { | const nodeTypes = { | ||||
| [CUSTOM_NODE]: CustomNode, | [CUSTOM_NODE]: CustomNode, | ||||
| data, | data, | ||||
| isLoading, | isLoading, | ||||
| } = useWorkflowInit() | } = useWorkflowInit() | ||||
| const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) | |||||
| const nodesData = useMemo(() => { | const nodesData = useMemo(() => { | ||||
| if (data) | if (data) | ||||
| allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), | allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), | ||||
| allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], | allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], | ||||
| number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, | number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, | ||||
| fileUploadConfig: fileUploadConfigResponse, | |||||
| }, | }, | ||||
| opening: { | opening: { | ||||
| enabled: !!features.opening_statement, | enabled: !!features.opening_statement, |
| import { Line3 } from '@/app/components/base/icons/src/public/common' | import { Line3 } from '@/app/components/base/icons/src/public/common' | ||||
| import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' | import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' | ||||
| import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' | import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' | ||||
| import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| type Props = { | type Props = { | ||||
| onChange(null) | onChange(null) | ||||
| }} | }} | ||||
| fileConfig={{ | fileConfig={{ | ||||
| allowed_file_types: inStepRun ? [SupportUploadFileTypes.custom] : payload.allowed_file_types, | |||||
| allowed_file_extensions: inStepRun ? [] : payload.allowed_file_extensions, | |||||
| allowed_file_types: inStepRun | |||||
| ? [ | |||||
| SupportUploadFileTypes.image, | |||||
| SupportUploadFileTypes.document, | |||||
| SupportUploadFileTypes.audio, | |||||
| SupportUploadFileTypes.video, | |||||
| ] | |||||
| : payload.allowed_file_types, | |||||
| allowed_file_extensions: inStepRun | |||||
| ? [ | |||||
| ...FILE_EXTS[SupportUploadFileTypes.image], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.document], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.audio], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.video], | |||||
| ] | |||||
| : payload.allowed_file_extensions, | |||||
| allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, | allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, | ||||
| number_limits: 1, | number_limits: 1, | ||||
| fileUploadConfig: fileSettings?.fileUploadConfig, | |||||
| }} | }} | ||||
| /> | /> | ||||
| )} | )} | ||||
| value={value} | value={value} | ||||
| onChange={files => onChange(files)} | onChange={files => onChange(files)} | ||||
| fileConfig={{ | fileConfig={{ | ||||
| allowed_file_types: inStepRun ? [SupportUploadFileTypes.custom] : payload.allowed_file_types, | |||||
| allowed_file_extensions: inStepRun ? [] : payload.allowed_file_extensions, | |||||
| allowed_file_types: inStepRun | |||||
| ? [ | |||||
| SupportUploadFileTypes.image, | |||||
| SupportUploadFileTypes.document, | |||||
| SupportUploadFileTypes.audio, | |||||
| SupportUploadFileTypes.video, | |||||
| ] | |||||
| : payload.allowed_file_types, | |||||
| allowed_file_extensions: inStepRun | |||||
| ? [ | |||||
| ...FILE_EXTS[SupportUploadFileTypes.image], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.document], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.audio], | |||||
| ...FILE_EXTS[SupportUploadFileTypes.video], | |||||
| ] | |||||
| : payload.allowed_file_extensions, | |||||
| allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, | allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, | ||||
| number_limits: inStepRun ? 5 : payload.max_length, | number_limits: inStepRun ? 5 : payload.max_length, | ||||
| fileUploadConfig: fileSettings?.fileUploadConfig, | |||||
| }} | }} | ||||
| /> | /> | ||||
| )} | )} |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useCallback } from 'react' | import React, { useCallback } from 'react' | ||||
| import useSWR from 'swr' | |||||
| import produce from 'immer' | import produce from 'immer' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import type { UploadFileSetting } from '../../../types' | import type { UploadFileSetting } from '../../../types' | ||||
| import InputNumberWithSlider from './input-number-with-slider' | import InputNumberWithSlider from './input-number-with-slider' | ||||
| import Field from '@/app/components/app/configuration/config-var/config-modal/field' | import Field from '@/app/components/app/configuration/config-var/config-modal/field' | ||||
| import { TransferMethod } from '@/types/app' | import { TransferMethod } from '@/types/app' | ||||
| import { FILE_SIZE_LIMIT } from '@/app/components/base/file-uploader/constants' | |||||
| import { fetchFileUploadConfig } from '@/service/common' | |||||
| import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' | |||||
| import { formatFileSize } from '@/utils/format' | import { formatFileSize } from '@/utils/format' | ||||
| type Props = { | type Props = { | ||||
| allowed_file_types, | allowed_file_types, | ||||
| allowed_file_extensions, | allowed_file_extensions, | ||||
| } = payload | } = payload | ||||
| const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) | |||||
| const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileUploadConfigResponse) | |||||
| const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => { | const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => { | ||||
| const newPayload = produce(payload, (draft) => { | const newPayload = produce(payload, (draft) => { | ||||
| title={t('appDebug.variableConfig.maxNumberOfUploads')!} | title={t('appDebug.variableConfig.maxNumberOfUploads')!} | ||||
| > | > | ||||
| <div> | <div> | ||||
| <div className='mb-1.5 text-text-tertiary body-xs-regular'>{t('appDebug.variableConfig.maxNumberTip', { size: formatFileSize(FILE_SIZE_LIMIT) })}</div> | |||||
| <div className='mb-1.5 text-text-tertiary body-xs-regular'>{t('appDebug.variableConfig.maxNumberTip', { | |||||
| imgLimit: formatFileSize(imgSizeLimit), | |||||
| docLimit: formatFileSize(docSizeLimit), | |||||
| audioLimit: formatFileSize(audioSizeLimit), | |||||
| videoLimit: formatFileSize(videoSizeLimit), | |||||
| })}</div> | |||||
| <InputNumberWithSlider | <InputNumberWithSlider | ||||
| value={max_length} | value={max_length} | ||||
| min={1} | min={1} |
| 'localUpload': 'Local Upload', | 'localUpload': 'Local Upload', | ||||
| 'both': 'Both', | 'both': 'Both', | ||||
| 'maxNumberOfUploads': 'Max number of uploads', | 'maxNumberOfUploads': 'Max number of uploads', | ||||
| 'maxNumberTip': 'Max {{size}} each', | |||||
| 'maxNumberTip': 'Document < {{docLimit}}, image < {{imgLimit}}, audio < {{audioLimit}}, video < {{videoLimit}}', | |||||
| 'errorMsg': { | 'errorMsg': { | ||||
| labelNameRequired: 'Label name is required', | labelNameRequired: 'Label name is required', | ||||
| varNameCanBeRepeat: 'Variable name can not be repeated', | varNameCanBeRepeat: 'Variable name can not be repeated', |
| pasteFileLinkInputPlaceholder: 'Enter URL...', | pasteFileLinkInputPlaceholder: 'Enter URL...', | ||||
| uploadFromComputerReadError: 'File reading failed, please try again.', | uploadFromComputerReadError: 'File reading failed, please try again.', | ||||
| uploadFromComputerUploadError: 'File upload failed, please upload again.', | uploadFromComputerUploadError: 'File upload failed, please upload again.', | ||||
| uploadFromComputerLimit: 'Upload File cannot exceed {{size}}', | |||||
| uploadFromComputerLimit: 'Upload {{type}} cannot exceed {{size}}', | |||||
| pasteFileLinkInvalid: 'Invalid file link', | pasteFileLinkInvalid: 'Invalid file link', | ||||
| fileExtensionNotSupport: 'File extension not supported', | fileExtensionNotSupport: 'File extension not supported', | ||||
| }, | }, |
| 'localUpload': '本地上传', | 'localUpload': '本地上传', | ||||
| 'both': '两者', | 'both': '两者', | ||||
| 'maxNumberOfUploads': '最大上传数', | 'maxNumberOfUploads': '最大上传数', | ||||
| 'maxNumberTip': '最大上传文件大小为 {{size}}', | |||||
| 'maxNumberTip': '文档 < {{docLimit}}, 图片 < {{imgLimit}}, 音频 < {{audioLimit}}, 视频 < {{videoLimit}}', | |||||
| 'content': '内容', | 'content': '内容', | ||||
| 'errorMsg': { | 'errorMsg': { | ||||
| labelNameRequired: '显示名称必填', | labelNameRequired: '显示名称必填', |
| pasteFileLinkInputPlaceholder: '输入文件链接', | pasteFileLinkInputPlaceholder: '输入文件链接', | ||||
| uploadFromComputerReadError: '文件读取失败,请重新选择。', | uploadFromComputerReadError: '文件读取失败,请重新选择。', | ||||
| uploadFromComputerUploadError: '文件上传失败,请重新上传。', | uploadFromComputerUploadError: '文件上传失败,请重新上传。', | ||||
| uploadFromComputerLimit: '上传文件不能超过 {{size}}', | |||||
| uploadFromComputerLimit: '上传 {{type}} 不能超过 {{size}}', | |||||
| pasteFileLinkInvalid: '文件链接无效', | pasteFileLinkInvalid: '文件链接无效', | ||||
| fileExtensionNotSupport: '文件类型不支持', | fileExtensionNotSupport: '文件类型不支持', | ||||
| }, | }, |
| } | } | ||||
| export type FileUploadConfigResponse = { | export type FileUploadConfigResponse = { | ||||
| file_size_limit: number | |||||
| batch_count_limit: number | batch_count_limit: number | ||||
| image_file_size_limit?: number | string | |||||
| image_file_size_limit?: number | string // default is 10MB | |||||
| file_size_limit: number // default is 15MB | |||||
| audio_file_size_limit?: number // default is 50MB | |||||
| video_file_size_limit?: number // default is 100MB | |||||
| } | } | ||||
| export type InvitationResult = { | export type InvitationResult = { |