"""Patch ChatInput.tsx: support uploading multiple files at once.
Changes:
1. Add `multiple` to the 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 = ''
new_input = ''
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) => {
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()