File size: 4,726 Bytes
5150658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""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()