Merge branch 'fix/file-size-limits' into deploy/dev
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions

This commit is contained in:
JzoNg 2024-10-23 22:38:05 +08:00
commit fe7e059e77
28 changed files with 385 additions and 1044 deletions

View File

@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field( CURRENT_VERSION: str = Field(
description="Dify version", description="Dify version",
default="0.10.0", default="0.10.1",
) )
COMMIT_SHA: str = Field( COMMIT_SHA: str = Field(

View File

@ -27,6 +27,7 @@ from core.app.task_pipeline.easy_ui_based_generate_task_pipeline import EasyUIBa
from core.prompt.utils.prompt_template_parser import PromptTemplateParser from core.prompt.utils.prompt_template_parser import PromptTemplateParser
from extensions.ext_database import db from extensions.ext_database import db
from models import Account from models import Account
from models.enums import CreatedByRole
from models.model import App, AppMode, AppModelConfig, Conversation, EndUser, Message, MessageFile from models.model import App, AppMode, AppModelConfig, Conversation, EndUser, Message, MessageFile
from services.errors.app_model_config import AppModelConfigBrokenError from services.errors.app_model_config import AppModelConfigBrokenError
from services.errors.conversation import ConversationCompletedError, ConversationNotExistsError from services.errors.conversation import ConversationCompletedError, ConversationNotExistsError
@ -240,7 +241,7 @@ class MessageBasedAppGenerator(BaseAppGenerator):
belongs_to="user", belongs_to="user",
url=file.remote_url, url=file.remote_url,
upload_file_id=file.related_id, upload_file_id=file.related_id,
created_by_role=("account" if account_id else "end_user"), created_by_role=(CreatedByRole.ACCOUNT if account_id else CreatedByRole.END_USER),
created_by=account_id or end_user_id or "", created_by=account_id or end_user_id or "",
) )
db.session.add(message_file) db.session.add(message_file)

1155
api/poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -172,12 +172,11 @@ sagemaker = "2.231.0"
scikit-learn = "~1.5.1" scikit-learn = "~1.5.1"
sentry-sdk = { version = "~1.44.1", extras = ["flask"] } sentry-sdk = { version = "~1.44.1", extras = ["flask"] }
sqlalchemy = "~2.0.29" sqlalchemy = "~2.0.29"
starlette = "0.41.0"
tencentcloud-sdk-python-hunyuan = "~3.0.1158" tencentcloud-sdk-python-hunyuan = "~3.0.1158"
tiktoken = "~0.8.0" tiktoken = "~0.8.0"
tokenizers = "~0.15.0" tokenizers = "~0.15.0"
transformers = "~4.35.0" transformers = "~4.35.0"
unstructured = { version = "~0.15.7", extras = ["docx", "epub", "md", "msg", "ppt", "pptx", "pdf"] } unstructured = { version = "~0.10.27", extras = ["docx", "epub", "md", "msg", "ppt", "pptx"] }
validators = "0.21.0" validators = "0.21.0"
volcengine-python-sdk = {extras = ["ark"], version = "~1.0.98"} volcengine-python-sdk = {extras = ["ark"], version = "~1.0.98"}
websocket-client = "~1.7.0" websocket-client = "~1.7.0"
@ -207,7 +206,7 @@ duckduckgo-search = "~6.3.0"
jsonpath-ng = "1.6.1" jsonpath-ng = "1.6.1"
matplotlib = "~3.8.2" matplotlib = "~3.8.2"
newspaper3k = "0.2.8" newspaper3k = "0.2.8"
nltk = "3.9.1" nltk = "3.8.1"
numexpr = "~2.9.0" numexpr = "~2.9.0"
pydub = "~0.25.1" pydub = "~0.25.1"
qrcode = "~7.4.2" qrcode = "~7.4.2"

View File

@ -6,4 +6,9 @@ pytest api/tests/integration_tests/vdb/chroma \
api/tests/integration_tests/vdb/pgvecto_rs \ api/tests/integration_tests/vdb/pgvecto_rs \
api/tests/integration_tests/vdb/pgvector \ api/tests/integration_tests/vdb/pgvector \
api/tests/integration_tests/vdb/qdrant \ api/tests/integration_tests/vdb/qdrant \
api/tests/integration_tests/vdb/weaviate api/tests/integration_tests/vdb/weaviate \
api/tests/integration_tests/vdb/elasticsearch \
api/tests/integration_tests/vdb/vikingdb \
api/tests/integration_tests/vdb/baidu \
api/tests/integration_tests/vdb/tcvectordb \
api/tests/integration_tests/vdb/upstash

View File

@ -2,7 +2,7 @@ version: '3'
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.10.0 image: langgenius/dify-api:0.10.1
restart: always restart: always
environment: environment:
# Startup mode, 'api' starts the API server. # Startup mode, 'api' starts the API server.
@ -227,7 +227,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.10.0 image: langgenius/dify-api:0.10.1
restart: always restart: always
environment: environment:
CONSOLE_WEB_URL: '' CONSOLE_WEB_URL: ''
@ -396,7 +396,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.10.0 image: langgenius/dify-web:0.10.1
restart: always restart: always
environment: environment:
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is # The base URL of console application api server, refers to the Console base URL of WEB service if console domain is

View File

@ -241,7 +241,7 @@ x-shared-env: &shared-api-worker-env
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.10.0 image: langgenius/dify-api:0.10.1
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -261,7 +261,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.10.0 image: langgenius/dify-api:0.10.1
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -280,7 +280,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.10.0 image: langgenius/dify-web:0.10.1
restart: always restart: always
environment: environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-} CONSOLE_API_URL: ${CONSOLE_API_URL:-}

View File

@ -1,6 +1,5 @@
'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'
@ -39,7 +38,6 @@ import { promptVariablesToUserInputsForm } from '@/utils/model-config'
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'
@ -94,7 +92,6 @@ const Debug: FC<IDebug> = ({
} = 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 () => {
@ -452,7 +449,7 @@ const Debug: FC<IDebug> = ({
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}
/> />

View File

@ -1,6 +1,7 @@
'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'
@ -69,6 +70,7 @@ import type { Features as FeaturesData, FileUpload } from '@/app/components/base
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
@ -84,6 +86,8 @@ const Configuration: FC = () => {
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()
@ -462,12 +466,13 @@ const Configuration: FC = () => {
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)
@ -684,6 +689,9 @@ const Configuration: FC = () => {
}, },
})) }))
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
@ -700,7 +708,7 @@ const Configuration: FC = () => {
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: {

View File

@ -40,6 +40,10 @@ const ChatWrapper = () => {
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

View File

@ -9,6 +9,7 @@ import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uplo
const Form = () => { const Form = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
appParams,
inputsForms, inputsForms,
newConversationInputs, newConversationInputs,
newConversationInputsRef, newConversationInputsRef,
@ -61,6 +62,7 @@ const Form = () => {
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,
}} }}
/> />
) )
@ -75,6 +77,7 @@ const Form = () => {
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,
}} }}
/> />
) )

View File

@ -42,6 +42,10 @@ const ChatWrapper = () => {
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

View File

@ -9,6 +9,7 @@ import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uplo
const Form = () => { const Form = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
appParams,
inputsForms, inputsForms,
newConversationInputs, newConversationInputs,
newConversationInputsRef, newConversationInputsRef,
@ -73,6 +74,7 @@ const Form = () => {
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,
}} }}
/> />
) )
@ -87,6 +89,7 @@ const Form = () => {
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,
}} }}
/> />
) )

View File

@ -1,4 +1,5 @@
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
@ -38,6 +39,7 @@ export type FileUpload = {
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 = {

View File

@ -1,3 +1,7 @@
// 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):\/\//

View File

@ -14,19 +14,113 @@ import {
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 {
@ -117,12 +211,15 @@ export const useFile = (fileConfig: FileUpload) => {
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(() => { }, [])
@ -140,13 +237,13 @@ export const useFile = (fileConfig: FileUpload) => {
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
return return
} }
if (file.size > FILE_SIZE_LIMIT) { const allowedFileTypes = fileConfig.allowed_file_types
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerLimit', { size: formatFileSize(FILE_SIZE_LIMIT) }) }) 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',
@ -187,7 +284,7 @@ export const useFile = (fileConfig: FileUpload) => {
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]

View File

@ -390,6 +390,7 @@ const TextGeneration: FC<IMainProps> = ({
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({

View File

@ -96,13 +96,19 @@ const RunOnce: FC<IRunOnceProps> = ({
{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>

View File

@ -3,7 +3,7 @@
import { SWRConfig } from 'swr' import { SWRConfig } from 'swr'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { useRouter, useSearchParams } from 'next/navigation' import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import useRefreshToken from '@/hooks/use-refresh-token' import useRefreshToken from '@/hooks/use-refresh-token'
import { fetchSetupStatus } from '@/service/common' import { fetchSetupStatus } from '@/service/common'
@ -15,6 +15,7 @@ const SwrInitor = ({
}: SwrInitorProps) => { }: SwrInitorProps) => {
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const pathname = usePathname()
const { getNewAccessToken } = useRefreshToken() const { getNewAccessToken } = useRefreshToken()
const consoleToken = searchParams.get('access_token') const consoleToken = searchParams.get('access_token')
const refreshToken = searchParams.get('refresh_token') const refreshToken = searchParams.get('refresh_token')
@ -68,13 +69,16 @@ const SwrInitor = ({
return return
} }
await setRefreshToken() await setRefreshToken()
if (searchParams.has('access_token') || searchParams.has('refresh_token'))
router.replace(pathname)
setInit(true) setInit(true)
} }
catch (error) { catch (error) {
router.replace('/signin') router.replace('/signin')
} }
})() })()
}, [isSetupFinished, setRefreshToken, router]) }, [isSetupFinished, setRefreshToken, router, pathname, searchParams])
return init return init
? ( ? (

View File

@ -9,6 +9,7 @@ import {
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import useSWR from 'swr'
import { setAutoFreeze } from 'immer' import { setAutoFreeze } from 'immer'
import { import {
useEventListener, useEventListener,
@ -93,6 +94,7 @@ import { useFeaturesStore } from '@/app/components/base/features/hooks'
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,
@ -382,6 +384,7 @@ const WorkflowWrap = memo(() => {
data, data,
isLoading, isLoading,
} = useWorkflowInit() } = useWorkflowInit()
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
const nodesData = useMemo(() => { const nodesData = useMemo(() => {
if (data) if (data)
@ -417,6 +420,7 @@ const WorkflowWrap = memo(() => {
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,

View File

@ -22,6 +22,7 @@ import { VarBlockIcon } from '@/app/components/workflow/block-icon'
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 = {
@ -168,10 +169,25 @@ const FormItem: FC<Props> = ({
onChange(null) onChange(null)
}} }}
fileConfig={{ fileConfig={{
allowed_file_types: inStepRun ? [SupportUploadFileTypes.custom] : payload.allowed_file_types, allowed_file_types: inStepRun
allowed_file_extensions: inStepRun ? [] : payload.allowed_file_extensions, ? [
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,
}} }}
/> />
)} )}
@ -180,10 +196,25 @@ const FormItem: FC<Props> = ({
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_types: inStepRun
allowed_file_extensions: inStepRun ? [] : payload.allowed_file_extensions, ? [
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,
}} }}
/> />
)} )}

View File

@ -1,6 +1,7 @@
'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'
@ -10,7 +11,8 @@ import FileTypeItem from './file-type-item'
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 = {
@ -36,6 +38,8 @@ const FileUploadSetting: FC<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) => {
@ -142,7 +146,13 @@ const FileUploadSetting: FC<Props> = ({
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}

View File

@ -386,7 +386,7 @@ const translation = {
'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',

View File

@ -572,7 +572,7 @@ const translation = {
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',
}, },

View File

@ -379,7 +379,7 @@ const translation = {
'localUpload': '本地上传', 'localUpload': '本地上传',
'both': '两者', 'both': '两者',
'maxNumberOfUploads': '最大上传数', 'maxNumberOfUploads': '最大上传数',
'maxNumberTip': '最大上传文件大小为 {{size}}', 'maxNumberTip': '文档 < {{docLimit}}, 图片 < {{imgLimit}}, 音频 < {{audioLimit}}, 视频 < {{videoLimit}}',
'content': '内容', 'content': '内容',
'errorMsg': { 'errorMsg': {
labelNameRequired: '显示名称必填', labelNameRequired: '显示名称必填',

View File

@ -377,7 +377,7 @@ const translation = {
addConfig: '增加配置', addConfig: '增加配置',
editConfig: '修改配置', editConfig: '修改配置',
loadBalancingLeastKeyWarning: '至少启用 2 个 Key 以使用负载均衡', loadBalancingLeastKeyWarning: '至少启用 2 个 Key 以使用负载均衡',
loadBalancingInfo: '默认情况下,负载衡使用 Round-robin 策略。如果触发速率限制,将应用 1 分钟的冷却时间', loadBalancingInfo: '默认情况下,负载衡使用 Round-robin 策略。如果触发速率限制,将应用 1 分钟的冷却时间',
upgradeForLoadBalancing: '升级以解锁负载均衡功能', upgradeForLoadBalancing: '升级以解锁负载均衡功能',
apiKey: 'API 密钥', apiKey: 'API 密钥',
}, },
@ -572,7 +572,7 @@ const translation = {
pasteFileLinkInputPlaceholder: '输入文件链接', pasteFileLinkInputPlaceholder: '输入文件链接',
uploadFromComputerReadError: '文件读取失败,请重新选择。', uploadFromComputerReadError: '文件读取失败,请重新选择。',
uploadFromComputerUploadError: '文件上传失败,请重新上传。', uploadFromComputerUploadError: '文件上传失败,请重新上传。',
uploadFromComputerLimit: '上传文件不能超过 {{size}}', uploadFromComputerLimit: '上传 {{type}} 不能超过 {{size}}',
pasteFileLinkInvalid: '文件链接无效', pasteFileLinkInvalid: '文件链接无效',
fileExtensionNotSupport: '文件类型不支持', fileExtensionNotSupport: '文件类型不支持',
}, },

View File

@ -211,9 +211,12 @@ export type PluginProvider = {
} }
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 = {

View File

@ -1,6 +1,6 @@
{ {
"name": "dify-web", "name": "dify-web",
"version": "0.10.0", "version": "0.10.1",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=18.17.0" "node": ">=18.17.0"