Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +31 -29
web/src/App.tsx
CHANGED
|
@@ -890,34 +890,40 @@ function App() {
|
|
| 890 |
// =========================
|
| 891 |
// ✅ File Upload (FIXED)
|
| 892 |
// =========================
|
| 893 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 894 |
const newFiles: UploadedFile[] = files.map((file) => ({ file, type: "other" as FileType }));
|
| 895 |
setUploadedFiles((prev) => [...prev, ...newFiles]);
|
| 896 |
};
|
| 897 |
-
|
| 898 |
-
//
|
| 899 |
-
const handleRemoveFile = (arg:
|
| 900 |
setUploadedFiles((prev) => {
|
| 901 |
if (!prev.length) return prev;
|
| 902 |
|
| 903 |
let idx = -1;
|
| 904 |
|
| 905 |
-
// Case 1: ChatArea passes index
|
| 906 |
if (typeof arg === "number") {
|
| 907 |
idx = arg;
|
| 908 |
} else {
|
| 909 |
-
// Case 2: ChatArea passes File or UploadedFile
|
| 910 |
const file =
|
| 911 |
-
|
| 912 |
? (arg as UploadedFile).file
|
| 913 |
-
:
|
|
|
|
|
|
|
| 914 |
|
| 915 |
-
|
| 916 |
-
(
|
| 917 |
-
x
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
|
|
|
|
|
|
| 921 |
}
|
| 922 |
|
| 923 |
if (idx < 0 || idx >= prev.length) return prev;
|
|
@@ -925,7 +931,6 @@ function App() {
|
|
| 925 |
const removed = prev[idx]?.file;
|
| 926 |
const next = prev.filter((_, i) => i !== idx);
|
| 927 |
|
| 928 |
-
// keep fingerprint set consistent
|
| 929 |
if (removed) {
|
| 930 |
const fp = `${removed.name}::${removed.size}::${removed.lastModified}`;
|
| 931 |
uploadedFingerprintsRef.current.delete(fp);
|
|
@@ -934,25 +939,22 @@ function App() {
|
|
| 934 |
return next;
|
| 935 |
});
|
| 936 |
};
|
| 937 |
-
|
| 938 |
-
//
|
| 939 |
const handleFileTypeChange = async (index: number, type: FileType) => {
|
| 940 |
if (!user) return;
|
| 941 |
-
|
| 942 |
const target = uploadedFiles[index]?.file;
|
| 943 |
if (!target) return;
|
| 944 |
-
|
| 945 |
-
// update UI
|
| 946 |
-
setUploadedFiles((prev) => {
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
});
|
| 950 |
-
|
| 951 |
-
// de-dupe upload per file fingerprint
|
| 952 |
const fp = `${target.name}::${target.size}::${target.lastModified}`;
|
| 953 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
| 954 |
uploadedFingerprintsRef.current.add(fp);
|
| 955 |
-
|
| 956 |
try {
|
| 957 |
await apiUpload({
|
| 958 |
user_id: user.email,
|
|
@@ -961,9 +963,9 @@ function App() {
|
|
| 961 |
});
|
| 962 |
toast.success("File uploaded to backend");
|
| 963 |
} catch (e: any) {
|
| 964 |
-
// allow retry
|
| 965 |
-
uploadedFingerprintsRef.current.delete(fp);
|
| 966 |
toast.error(e?.message || "Upload failed");
|
|
|
|
|
|
|
| 967 |
}
|
| 968 |
};
|
| 969 |
|
|
|
|
| 890 |
// =========================
|
| 891 |
// ✅ File Upload (FIXED)
|
| 892 |
// =========================
|
| 893 |
+
// 1) Upload: accept File[] OR FileList OR null
|
| 894 |
+
const handleFileUpload = (input: File[] | FileList | null | undefined) => {
|
| 895 |
+
const files = Array.isArray(input) ? input : input ? Array.from(input) : [];
|
| 896 |
+
if (!files.length) return;
|
| 897 |
+
|
| 898 |
const newFiles: UploadedFile[] = files.map((file) => ({ file, type: "other" as FileType }));
|
| 899 |
setUploadedFiles((prev) => [...prev, ...newFiles]);
|
| 900 |
};
|
| 901 |
+
|
| 902 |
+
// 2) Remove: accept index OR File OR UploadedFile (use any to avoid prop signature mismatch crash)
|
| 903 |
+
const handleRemoveFile = (arg: any) => {
|
| 904 |
setUploadedFiles((prev) => {
|
| 905 |
if (!prev.length) return prev;
|
| 906 |
|
| 907 |
let idx = -1;
|
| 908 |
|
|
|
|
| 909 |
if (typeof arg === "number") {
|
| 910 |
idx = arg;
|
| 911 |
} else {
|
|
|
|
| 912 |
const file =
|
| 913 |
+
arg?.file instanceof File
|
| 914 |
? (arg as UploadedFile).file
|
| 915 |
+
: arg instanceof File
|
| 916 |
+
? (arg as File)
|
| 917 |
+
: null;
|
| 918 |
|
| 919 |
+
if (file) {
|
| 920 |
+
idx = prev.findIndex(
|
| 921 |
+
(x) =>
|
| 922 |
+
x.file.name === file.name &&
|
| 923 |
+
x.file.size === file.size &&
|
| 924 |
+
x.file.lastModified === file.lastModified
|
| 925 |
+
);
|
| 926 |
+
}
|
| 927 |
}
|
| 928 |
|
| 929 |
if (idx < 0 || idx >= prev.length) return prev;
|
|
|
|
| 931 |
const removed = prev[idx]?.file;
|
| 932 |
const next = prev.filter((_, i) => i !== idx);
|
| 933 |
|
|
|
|
| 934 |
if (removed) {
|
| 935 |
const fp = `${removed.name}::${removed.size}::${removed.lastModified}`;
|
| 936 |
uploadedFingerprintsRef.current.delete(fp);
|
|
|
|
| 939 |
return next;
|
| 940 |
});
|
| 941 |
};
|
| 942 |
+
|
| 943 |
+
// 3) Type Change: must be async, and must NOT leave "await" outside
|
| 944 |
const handleFileTypeChange = async (index: number, type: FileType) => {
|
| 945 |
if (!user) return;
|
| 946 |
+
|
| 947 |
const target = uploadedFiles[index]?.file;
|
| 948 |
if (!target) return;
|
| 949 |
+
|
| 950 |
+
// update UI state immediately
|
| 951 |
+
setUploadedFiles((prev) => prev.map((f, i) => (i === index ? { ...f, type } : f)));
|
| 952 |
+
|
| 953 |
+
// dedupe upload per file fingerprint
|
|
|
|
|
|
|
|
|
|
| 954 |
const fp = `${target.name}::${target.size}::${target.lastModified}`;
|
| 955 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
| 956 |
uploadedFingerprintsRef.current.add(fp);
|
| 957 |
+
|
| 958 |
try {
|
| 959 |
await apiUpload({
|
| 960 |
user_id: user.email,
|
|
|
|
| 963 |
});
|
| 964 |
toast.success("File uploaded to backend");
|
| 965 |
} catch (e: any) {
|
|
|
|
|
|
|
| 966 |
toast.error(e?.message || "Upload failed");
|
| 967 |
+
// allow retry if failed
|
| 968 |
+
uploadedFingerprintsRef.current.delete(fp);
|
| 969 |
}
|
| 970 |
};
|
| 971 |
|