Spaces:
Running
Running
| """Patch ChatInput.tsx: support uploading multiple files at once. | |
| Changes: | |
| 1. Add `multiple` to the <input type="file"> so the user can select many | |
| files/images in one pick. | |
| 2. Rewrite handleDatasetFileChange to send ALL selected files in a single | |
| multipart request (backend returns list[DatasetUploadResponse]). | |
| """ | |
| import os | |
| FILE = "/source/frontend/src/components/Chat/ChatInput.tsx" | |
| def patch(): | |
| if not os.path.exists(FILE): | |
| print(f"SKIP: {FILE} not found") | |
| return | |
| with open(FILE, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| # ---- 1. Add `multiple` to the file input ---- | |
| old_input = '<input\n ref={fileInputRef}\n type="file"\n accept={DATASET_UPLOAD_ACCEPT}\n onChange={handleDatasetFileChange}\n style={{ display: \'none\' }}\n />' | |
| new_input = '<input\n ref={fileInputRef}\n type="file"\n accept={DATASET_UPLOAD_ACCEPT}\n multiple\n onChange={handleDatasetFileChange}\n style={{ display: \'none\' }}\n />' | |
| if old_input in content: | |
| content = content.replace(old_input, new_input) | |
| print("OK: added multiple to file input") | |
| else: | |
| print("WARN: file input block not matched exactly") | |
| # ---- 2. Rewrite handleDatasetFileChange ---- | |
| old_handler_start = " const handleDatasetFileChange = useCallback(" | |
| start = content.find(old_handler_start) | |
| if start == -1: | |
| print("FAIL: handleDatasetFileChange not found") | |
| return | |
| # End of the handler: the " );" right after the deps line. | |
| deps = " [sessionId, onDatasetUploaded],\n );" | |
| end = content.find(deps, start) | |
| if end != -1: | |
| end += len(deps) | |
| else: | |
| print("FAIL: could not find end of handler") | |
| return | |
| new_handler = ''' const handleDatasetFileChange = useCallback( | |
| async (event: React.ChangeEvent<HTMLInputElement>) => { | |
| const files = Array.from(event.target.files ?? []); | |
| event.target.value = ''; | |
| if (!files.length) return; | |
| if (!sessionId) { | |
| setDatasetUploadError('Start a session before uploading files.'); | |
| return; | |
| } | |
| // Validate each file before uploading the batch. | |
| for (const file of files) { | |
| const ext = file.name.split('.').pop()?.toLowerCase() || ''; | |
| if (!DATASET_UPLOAD_EXTENSIONS.has(ext)) { | |
| setDatasetUploadError('Không hỗ trợ định dạng này. Các định dạng được hỗ trợ: CSV, JSON, JSONL, PDF, ảnh (PNG/JPG/JPEG/WebP/BMP/TIFF/GIF), Excel (XLSX/XLSM/XLS), Word (DOCX/DOC), PowerPoint (PPTX), OpenDocument (ODT/ODS/ODP), và văn bản (TXT/MD/RTF/LOG).'); | |
| return; | |
| } | |
| if (file.size > MAX_DATASET_UPLOAD_BYTES) { | |
| setDatasetUploadError(`File must be 100 MB or smaller. ${file.name} is ${formatBytes(file.size)}.`); | |
| return; | |
| } | |
| if (file.size === 0) { | |
| setDatasetUploadError(`Uploaded file is empty: ${file.name}`); | |
| return; | |
| } | |
| } | |
| // Send all files in one request; backend returns a list. | |
| const formData = new FormData(); | |
| for (const file of files) { | |
| formData.append('file', file); | |
| } | |
| setIsUploadingDataset(true); | |
| setDatasetUploadProgress(0); | |
| setDatasetUploadError(null); | |
| setDatasetUploadSuccess(null); | |
| try { | |
| const res = await apiUpload(`/api/session/${sessionId}/datasets`, formData, { | |
| onProgress: ({ percent }) => { | |
| setDatasetUploadProgress(percent !== null && percent < 100 ? percent : null); | |
| }, | |
| }); | |
| if (!res.ok) { | |
| setDatasetUploadError(await readApiErrorMessage(res, 'File upload failed.')); | |
| return; | |
| } | |
| const payload = await res.json() as DatasetUploadResponse[]; | |
| if (payload.length) { | |
| setUploadedDatasets((previous) => [...payload, ...previous]); | |
| const names = payload.map((u) => u.filename).join(', '); | |
| setDatasetUploadSuccess(`Đã tải lên ${payload.length} file: ${names}`); | |
| } | |
| await onDatasetUploaded?.(); | |
| } catch (error) { | |
| setDatasetUploadError( | |
| error instanceof Error ? error.message : 'File upload failed.' | |
| ); | |
| } finally { | |
| setIsUploadingDataset(false); | |
| setDatasetUploadProgress(null); | |
| } | |
| }, | |
| [sessionId, onDatasetUploaded], | |
| );''' | |
| content = content[:start] + new_handler + content[end:] | |
| with open(FILE, "w", encoding="utf-8") as f: | |
| f.write(content) | |
| print("OK: ChatInput.tsx multi-file upload patched") | |
| if __name__ == "__main__": | |
| patch() | |