/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/ChatInput/fileUpload/createDropHandler.ts
180 строк
5 KB
Nico Bellante
[feat] Replace inline error messages with tooltips in `st.chat_input` file upload UI (#13542)
11 янв 2026, 21:19
Не верифицирован
11 янв 2026, 21:19
6e7afbf
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { zip } from "lodash-es" import { ErrorCode as FileErrorCode, FileRejection } from "react-dropzone" import { ChatInput as ChatInputProto, FileURLs as FileURLsProto, IFileURLs, } from "@streamlit/protobuf" import { UploadFileInfo } from "~lib/components/widgets/FileUploader/UploadFileInfo" import { FileUploadClient } from "~lib/FileUploadClient" import { getRejectedFileInfo } from "~lib/util/FileHelper" import { validateFileType } from "./fileUploadUtils" interface CreateDropHandlerParams { acceptMultipleFiles: boolean maxFileSize: number uploadClient: FileUploadClient uploadFile: (fileURLs: FileURLsProto, file: File) => void addFiles: (files: UploadFileInfo[]) => void getNextLocalFileId: () => number deleteExistingFiles: () => void onUploadComplete: () => void element: ChatInputProto } /** * Validates files against type and size constraints, separating them into * accepted and rejected lists. This validation is necessary because: * 1. Directory uploads (webkitdirectory) bypass react-dropzone's validation * 2. Retry uploads call the drop handler directly, bypassing react-dropzone */ const filterFiles = ( files: File[], element: ChatInputProto, maxFileSize: number ): { accepted: File[]; rejected: FileRejection[] } => { const accepted: File[] = [] const rejected: FileRejection[] = [] files.forEach(file => { // Check file size first if (file.size > maxFileSize) { rejected.push({ file, errors: [ { code: FileErrorCode.FileTooLarge, message: `File is too large. Maximum size is ${maxFileSize} bytes.`, }, ], }) return } // Check file type const validation = validateFileType(file, element.fileType) if (!validation.isValid) { rejected.push({ file, errors: [ { code: FileErrorCode.FileInvalidType, message: validation.errorMessage || "File type not allowed.", }, ], }) return } accepted.push(file) }) return { accepted, rejected } } export const createDropHandler = ({ acceptMultipleFiles, maxFileSize, uploadClient, uploadFile, addFiles, getNextLocalFileId, deleteExistingFiles, onUploadComplete, element, }: CreateDropHandlerParams) => (acceptedFiles: File[], rejectedFiles: FileRejection[]): void => { // Always validate files - this catches files that bypass react-dropzone's // validation, such as: // 1. Directory uploads (webkitdirectory bypasses react-dropzone) // 2. Retry uploads (dropHandler is called directly, bypassing react-dropzone) if (acceptedFiles.length > 0) { const { accepted, rejected } = filterFiles( acceptedFiles, element, maxFileSize ) acceptedFiles = accepted rejectedFiles = [...rejectedFiles, ...rejected] } // If only single file upload is allowed but multiple were dropped/selected, // all files will be rejected by default. In this case, we take the first // valid file into acceptedFiles, and reject the rest. if ( !acceptMultipleFiles && acceptedFiles.length === 0 && rejectedFiles.length > 1 ) { const firstFileIndex = rejectedFiles.findIndex( // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- TODO: Fix this file => file.errors?.[0].code === FileErrorCode.TooManyFiles ) if (firstFileIndex >= 0) { acceptedFiles.push(rejectedFiles[firstFileIndex].file) rejectedFiles.splice(firstFileIndex, 1) } } if (!acceptMultipleFiles && acceptedFiles.length > 0) { deleteExistingFiles() } uploadClient .fetchFileURLs(acceptedFiles) .then((fileURLsArray: IFileURLs[]) => { zip(fileURLsArray, acceptedFiles).forEach( ([fileURLs, acceptedFile]) => { uploadFile(fileURLs as FileURLsProto, acceptedFile as File) } ) }) .catch((errorMessage: string) => { addFiles( acceptedFiles.map(f => { return new UploadFileInfo( f.name, f.size, getNextLocalFileId(), { type: "error", errorMessage, }, f ) }) ) }) // Create an UploadFileInfo for each of our rejected files, and add them to // our state. if (rejectedFiles.length > 0) { const rejectedInfos = rejectedFiles.map(rejected => getRejectedFileInfo(rejected, getNextLocalFileId(), maxFileSize) ) addFiles(rejectedInfos) } onUploadComplete() }