Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +120 -84
web/src/App.tsx
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
| 25 |
energyPct,
|
| 26 |
} from "./lib/reviewStar";
|
| 27 |
|
| 28 |
-
|
| 29 |
export type MessageAttachmentKind = "pdf" | "ppt" | "doc" | "image" | "other";
|
| 30 |
|
| 31 |
export interface MessageAttachment {
|
|
@@ -36,16 +35,21 @@ export interface MessageAttachment {
|
|
| 36 |
fileType?: FileType; // syllabus / lecture-slides / ...
|
| 37 |
}
|
| 38 |
|
|
|
|
|
|
|
| 39 |
export interface Message {
|
| 40 |
id: string;
|
| 41 |
role: "user" | "assistant";
|
| 42 |
content: string;
|
| 43 |
timestamp: Date;
|
|
|
|
|
|
|
|
|
|
| 44 |
references?: string[];
|
| 45 |
sender?: GroupMember;
|
| 46 |
showNextButton?: boolean;
|
| 47 |
|
| 48 |
-
// ✅
|
| 49 |
attachments?: MessageAttachment[];
|
| 50 |
|
| 51 |
questionData?: {
|
|
@@ -58,8 +62,6 @@ export interface Message {
|
|
| 58 |
};
|
| 59 |
}
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
export interface User {
|
| 64 |
// required identity
|
| 65 |
name: string;
|
|
@@ -155,6 +157,56 @@ function mapLanguagePref(lang: Language): string {
|
|
| 155 |
return "Auto";
|
| 156 |
}
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
// ✅ localStorage helpers for saved chats
|
| 159 |
function savedChatsStorageKey(email: string) {
|
| 160 |
return `saved_chats::${email}`;
|
|
@@ -654,6 +706,8 @@ function App() {
|
|
| 654 |
|
| 655 |
if (!hasText && !hasFiles) return;
|
| 656 |
|
|
|
|
|
|
|
| 657 |
const fileNames = hasFiles ? uploadedFiles.map((f) => f.file.name) : [];
|
| 658 |
const fileLine = fileNames.length ? `Uploaded files: ${fileNames.join(", ")}` : "";
|
| 659 |
|
|
@@ -672,29 +726,28 @@ function App() {
|
|
| 672 |
? content
|
| 673 |
: `📎 Sent ${fileNames.length} file(s)\n${fileNames.map((n) => `- ${n}`).join("\n")}`;
|
| 674 |
|
| 675 |
-
// ✅ snapshot attachments at send-time
|
| 676 |
-
const attachmentsSnapshot: MessageAttachment[] =
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
const userMessage: Message = {
|
| 699 |
id: Date.now().toString(),
|
| 700 |
role: "user",
|
|
@@ -720,32 +773,19 @@ function App() {
|
|
| 720 |
learning_mode: "quiz",
|
| 721 |
language_preference: mapLanguagePref(language),
|
| 722 |
doc_type: docType,
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
const normalizeRefs = (raw: any): string[] => {
|
| 726 |
-
const arr = Array.isArray(raw) ? raw : [];
|
| 727 |
-
return arr
|
| 728 |
-
.map((x) => {
|
| 729 |
-
if (typeof x === "string") {
|
| 730 |
-
const s = x.trim();
|
| 731 |
-
return s ? s : null;
|
| 732 |
-
}
|
| 733 |
-
const a = x?.source_file ? String(x.source_file) : "";
|
| 734 |
-
const b = x?.section ? String(x.section) : "";
|
| 735 |
-
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 736 |
-
return s || null;
|
| 737 |
-
})
|
| 738 |
-
.filter(Boolean) as string[];
|
| 739 |
-
};
|
| 740 |
|
| 741 |
-
const
|
|
|
|
| 742 |
|
| 743 |
const assistantMessage: Message = {
|
| 744 |
id: (Date.now() + 1).toString(),
|
| 745 |
role: "assistant",
|
| 746 |
-
content: r.reply || "",
|
| 747 |
timestamp: new Date(),
|
| 748 |
-
|
|
|
|
| 749 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 750 |
showNextButton: false,
|
| 751 |
};
|
|
@@ -786,23 +826,19 @@ function App() {
|
|
| 786 |
learning_mode: learningMode,
|
| 787 |
language_preference: mapLanguagePref(language),
|
| 788 |
doc_type: docType,
|
| 789 |
-
|
|
|
|
| 790 |
|
| 791 |
-
const
|
| 792 |
-
|
| 793 |
-
const a = x?.source_file ? String(x.source_file) : "";
|
| 794 |
-
const b = x?.section ? String(x.section) : "";
|
| 795 |
-
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 796 |
-
return s || null;
|
| 797 |
-
})
|
| 798 |
-
.filter(Boolean) as string[];
|
| 799 |
|
| 800 |
const assistantMessage: Message = {
|
| 801 |
id: (Date.now() + 1).toString(),
|
| 802 |
role: "assistant",
|
| 803 |
-
content: r.reply || "",
|
| 804 |
timestamp: new Date(),
|
| 805 |
-
|
|
|
|
| 806 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 807 |
};
|
| 808 |
|
|
@@ -841,6 +877,8 @@ function App() {
|
|
| 841 |
const handleNextQuestion = async () => {
|
| 842 |
if (!user) return;
|
| 843 |
|
|
|
|
|
|
|
| 844 |
const prompt = "Please give me another question of the same quiz style.";
|
| 845 |
const sender: GroupMember = {
|
| 846 |
id: user.email,
|
|
@@ -868,23 +906,19 @@ function App() {
|
|
| 868 |
learning_mode: "quiz",
|
| 869 |
language_preference: mapLanguagePref(language),
|
| 870 |
doc_type: docType,
|
| 871 |
-
|
|
|
|
| 872 |
|
| 873 |
-
const
|
| 874 |
-
|
| 875 |
-
const a = x?.source_file ? String(x.source_file) : "";
|
| 876 |
-
const b = x?.section ? String(x.section) : "";
|
| 877 |
-
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 878 |
-
return s || null;
|
| 879 |
-
})
|
| 880 |
-
.filter(Boolean) as string[];
|
| 881 |
|
| 882 |
const assistantMessage: Message = {
|
| 883 |
id: (Date.now() + 1).toString(),
|
| 884 |
role: "assistant",
|
| 885 |
-
content: r.reply || "",
|
| 886 |
timestamp: new Date(),
|
| 887 |
-
|
|
|
|
| 888 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 889 |
showNextButton: false,
|
| 890 |
};
|
|
@@ -919,6 +953,8 @@ function App() {
|
|
| 919 |
const handleStartQuiz = async () => {
|
| 920 |
if (!user) return;
|
| 921 |
|
|
|
|
|
|
|
| 922 |
setIsTyping(true);
|
| 923 |
try {
|
| 924 |
const docType = getCurrentDocTypeForChat();
|
|
@@ -928,23 +964,19 @@ function App() {
|
|
| 928 |
language_preference: mapLanguagePref(language),
|
| 929 |
doc_type: docType,
|
| 930 |
learning_mode: "quiz",
|
| 931 |
-
|
|
|
|
| 932 |
|
| 933 |
-
const
|
| 934 |
-
|
| 935 |
-
const a = x?.source_file ? String(x.source_file) : "";
|
| 936 |
-
const b = x?.section ? String(x.section) : "";
|
| 937 |
-
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 938 |
-
return s || null;
|
| 939 |
-
})
|
| 940 |
-
.filter(Boolean) as string[];
|
| 941 |
|
| 942 |
const assistantMessage: Message = {
|
| 943 |
id: Date.now().toString(),
|
| 944 |
role: "assistant",
|
| 945 |
-
content: r.reply || "",
|
| 946 |
timestamp: new Date(),
|
| 947 |
-
|
|
|
|
| 948 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 949 |
showNextButton: false,
|
| 950 |
};
|
|
@@ -974,7 +1006,6 @@ function App() {
|
|
| 974 |
// =========================
|
| 975 |
// File Upload (FIXED)
|
| 976 |
// =========================
|
| 977 |
-
|
| 978 |
const handleFileUpload = async (input: File[] | FileList | null | undefined) => {
|
| 979 |
const files = Array.isArray(input) ? input : input ? Array.from(input) : [];
|
| 980 |
if (!files.length) return;
|
|
@@ -985,6 +1016,8 @@ function App() {
|
|
| 985 |
|
| 986 |
if (!user) return;
|
| 987 |
|
|
|
|
|
|
|
| 988 |
for (const f of files) {
|
| 989 |
const fp = `${f.name}::${f.size}::${f.lastModified}`;
|
| 990 |
if (uploadedFingerprintsRef.current.has(fp)) continue;
|
|
@@ -994,8 +1027,9 @@ function App() {
|
|
| 994 |
await apiUpload({
|
| 995 |
user_id: user.email,
|
| 996 |
doc_type: DOC_TYPE_MAP["other"] || "Other Course Document",
|
|
|
|
| 997 |
file: f,
|
| 998 |
-
});
|
| 999 |
toast.success(`File uploaded: ${f.name}`);
|
| 1000 |
} catch (e: any) {
|
| 1001 |
toast.error(e?.message || `Upload failed: ${f.name}`);
|
|
@@ -1054,12 +1088,15 @@ function App() {
|
|
| 1054 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
| 1055 |
uploadedFingerprintsRef.current.add(fp);
|
| 1056 |
|
|
|
|
|
|
|
| 1057 |
try {
|
| 1058 |
await apiUpload({
|
| 1059 |
user_id: user.email,
|
| 1060 |
doc_type: DOC_TYPE_MAP[type] || "Other Course Document",
|
|
|
|
| 1061 |
file: target,
|
| 1062 |
-
});
|
| 1063 |
toast.success("File uploaded to backend");
|
| 1064 |
} catch (e: any) {
|
| 1065 |
toast.error(e?.message || "Upload failed");
|
|
@@ -1523,7 +1560,6 @@ function App() {
|
|
| 1523 |
onReviewActivity={handleReviewActivity}
|
| 1524 |
currentUserId={user?.email}
|
| 1525 |
docType={"Syllabus"}
|
| 1526 |
-
// ✅ bio is still allowed to be updated by chat/Clare
|
| 1527 |
onProfileBioUpdate={(bio) => updateUser({ bio })}
|
| 1528 |
/>
|
| 1529 |
</div>
|
|
|
|
| 25 |
energyPct,
|
| 26 |
} from "./lib/reviewStar";
|
| 27 |
|
|
|
|
| 28 |
export type MessageAttachmentKind = "pdf" | "ppt" | "doc" | "image" | "other";
|
| 29 |
|
| 30 |
export interface MessageAttachment {
|
|
|
|
| 35 |
fileType?: FileType; // syllabus / lecture-slides / ...
|
| 36 |
}
|
| 37 |
|
| 38 |
+
type RefObj = { source_file: string; section?: string };
|
| 39 |
+
|
| 40 |
export interface Message {
|
| 41 |
id: string;
|
| 42 |
role: "user" | "assistant";
|
| 43 |
content: string;
|
| 44 |
timestamp: Date;
|
| 45 |
+
// ✅ NEW: structured refs returned by backend
|
| 46 |
+
refs?: RefObj[];
|
| 47 |
+
// legacy string list (keep)
|
| 48 |
references?: string[];
|
| 49 |
sender?: GroupMember;
|
| 50 |
showNextButton?: boolean;
|
| 51 |
|
| 52 |
+
// ✅ show files “with” the user message (metadata only)
|
| 53 |
attachments?: MessageAttachment[];
|
| 54 |
|
| 55 |
questionData?: {
|
|
|
|
| 62 |
};
|
| 63 |
}
|
| 64 |
|
|
|
|
|
|
|
| 65 |
export interface User {
|
| 66 |
// required identity
|
| 67 |
name: string;
|
|
|
|
| 157 |
return "Auto";
|
| 158 |
}
|
| 159 |
|
| 160 |
+
// ✅ map UI course ids to backend course_id
|
| 161 |
+
const BACKEND_COURSE_ID_MAP: Record<string, string> = {
|
| 162 |
+
course1: "course_ist345",
|
| 163 |
+
course2: "course_ist345",
|
| 164 |
+
course3: "course_ist345",
|
| 165 |
+
course4: "course_ist345",
|
| 166 |
+
};
|
| 167 |
+
|
| 168 |
+
function getBackendCourseId(uiCourseId: string): string {
|
| 169 |
+
return BACKEND_COURSE_ID_MAP[uiCourseId] || "course_ist345";
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
function normalizeApiRefs(raw: any): RefObj[] {
|
| 173 |
+
const arr = Array.isArray(raw) ? raw : [];
|
| 174 |
+
return arr
|
| 175 |
+
.map((x) => {
|
| 176 |
+
// object form
|
| 177 |
+
if (x && typeof x === "object") {
|
| 178 |
+
const source = x.source_file ? String(x.source_file).trim() : "";
|
| 179 |
+
const section = x.section ? String(x.section).trim() : "";
|
| 180 |
+
if (!source && !section) return null;
|
| 181 |
+
return { source_file: source || "Unknown file", section: section || undefined };
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// legacy string
|
| 185 |
+
if (typeof x === "string") {
|
| 186 |
+
const s = x.trim();
|
| 187 |
+
if (!s) return null;
|
| 188 |
+
const parts = s.split("—").map((p) => p.trim()).filter(Boolean);
|
| 189 |
+
if (parts.length >= 2) {
|
| 190 |
+
return { source_file: parts[0] || "Unknown file", section: parts.slice(1).join(" — ") || undefined };
|
| 191 |
+
}
|
| 192 |
+
return { source_file: s, section: undefined };
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
return null;
|
| 196 |
+
})
|
| 197 |
+
.filter(Boolean) as RefObj[];
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
function refsToLegacyStrings(refs: RefObj[]): string[] {
|
| 201 |
+
return (refs || [])
|
| 202 |
+
.map((r) => {
|
| 203 |
+
const a = (r.source_file || "").trim();
|
| 204 |
+
const b = (r.section || "").trim();
|
| 205 |
+
return b ? `${a} — ${b}` : a;
|
| 206 |
+
})
|
| 207 |
+
.filter(Boolean);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
// ✅ localStorage helpers for saved chats
|
| 211 |
function savedChatsStorageKey(email: string) {
|
| 212 |
return `saved_chats::${email}`;
|
|
|
|
| 706 |
|
| 707 |
if (!hasText && !hasFiles) return;
|
| 708 |
|
| 709 |
+
const backendCourseId = getBackendCourseId(currentCourseId);
|
| 710 |
+
|
| 711 |
const fileNames = hasFiles ? uploadedFiles.map((f) => f.file.name) : [];
|
| 712 |
const fileLine = fileNames.length ? `Uploaded files: ${fileNames.join(", ")}` : "";
|
| 713 |
|
|
|
|
| 726 |
? content
|
| 727 |
: `📎 Sent ${fileNames.length} file(s)\n${fileNames.map((n) => `- ${n}`).join("\n")}`;
|
| 728 |
|
| 729 |
+
// ✅ snapshot attachments at send-time
|
| 730 |
+
const attachmentsSnapshot: MessageAttachment[] = uploadedFiles.map((uf) => {
|
| 731 |
+
const lower = uf.file.name.toLowerCase();
|
| 732 |
+
const kind: MessageAttachmentKind =
|
| 733 |
+
lower.endsWith(".pdf")
|
| 734 |
+
? "pdf"
|
| 735 |
+
: lower.endsWith(".ppt") || lower.endsWith(".pptx")
|
| 736 |
+
? "ppt"
|
| 737 |
+
: lower.endsWith(".doc") || lower.endsWith(".docx")
|
| 738 |
+
? "doc"
|
| 739 |
+
: [".jpg", ".jpeg", ".png", ".gif", ".webp"].some((e) => lower.endsWith(e))
|
| 740 |
+
? "image"
|
| 741 |
+
: "other";
|
| 742 |
+
|
| 743 |
+
return {
|
| 744 |
+
name: uf.file.name,
|
| 745 |
+
size: uf.file.size,
|
| 746 |
+
kind,
|
| 747 |
+
fileType: uf.type,
|
| 748 |
+
};
|
| 749 |
+
});
|
| 750 |
+
|
|
|
|
| 751 |
const userMessage: Message = {
|
| 752 |
id: Date.now().toString(),
|
| 753 |
role: "user",
|
|
|
|
| 773 |
learning_mode: "quiz",
|
| 774 |
language_preference: mapLanguagePref(language),
|
| 775 |
doc_type: docType,
|
| 776 |
+
course_id: backendCourseId,
|
| 777 |
+
} as any);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
|
| 779 |
+
const refsObj = normalizeApiRefs((r as any).refs ?? (r as any).references);
|
| 780 |
+
const refsLegacy = refsToLegacyStrings(refsObj);
|
| 781 |
|
| 782 |
const assistantMessage: Message = {
|
| 783 |
id: (Date.now() + 1).toString(),
|
| 784 |
role: "assistant",
|
| 785 |
+
content: (r as any).reply || "",
|
| 786 |
timestamp: new Date(),
|
| 787 |
+
refs: refsObj.length ? refsObj : undefined,
|
| 788 |
+
references: refsLegacy.length ? refsLegacy : undefined,
|
| 789 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 790 |
showNextButton: false,
|
| 791 |
};
|
|
|
|
| 826 |
learning_mode: learningMode,
|
| 827 |
language_preference: mapLanguagePref(language),
|
| 828 |
doc_type: docType,
|
| 829 |
+
course_id: backendCourseId,
|
| 830 |
+
} as any);
|
| 831 |
|
| 832 |
+
const refsObj = normalizeApiRefs((r as any).refs ?? (r as any).references);
|
| 833 |
+
const refsLegacy = refsToLegacyStrings(refsObj);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
|
| 835 |
const assistantMessage: Message = {
|
| 836 |
id: (Date.now() + 1).toString(),
|
| 837 |
role: "assistant",
|
| 838 |
+
content: (r as any).reply || "",
|
| 839 |
timestamp: new Date(),
|
| 840 |
+
refs: refsObj.length ? refsObj : undefined,
|
| 841 |
+
references: refsLegacy.length ? refsLegacy : undefined,
|
| 842 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 843 |
};
|
| 844 |
|
|
|
|
| 877 |
const handleNextQuestion = async () => {
|
| 878 |
if (!user) return;
|
| 879 |
|
| 880 |
+
const backendCourseId = getBackendCourseId(currentCourseId);
|
| 881 |
+
|
| 882 |
const prompt = "Please give me another question of the same quiz style.";
|
| 883 |
const sender: GroupMember = {
|
| 884 |
id: user.email,
|
|
|
|
| 906 |
learning_mode: "quiz",
|
| 907 |
language_preference: mapLanguagePref(language),
|
| 908 |
doc_type: docType,
|
| 909 |
+
course_id: backendCourseId,
|
| 910 |
+
} as any);
|
| 911 |
|
| 912 |
+
const refsObj = normalizeApiRefs((r as any).refs ?? (r as any).references);
|
| 913 |
+
const refsLegacy = refsToLegacyStrings(refsObj);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 914 |
|
| 915 |
const assistantMessage: Message = {
|
| 916 |
id: (Date.now() + 1).toString(),
|
| 917 |
role: "assistant",
|
| 918 |
+
content: (r as any).reply || "",
|
| 919 |
timestamp: new Date(),
|
| 920 |
+
refs: refsObj.length ? refsObj : undefined,
|
| 921 |
+
references: refsLegacy.length ? refsLegacy : undefined,
|
| 922 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 923 |
showNextButton: false,
|
| 924 |
};
|
|
|
|
| 953 |
const handleStartQuiz = async () => {
|
| 954 |
if (!user) return;
|
| 955 |
|
| 956 |
+
const backendCourseId = getBackendCourseId(currentCourseId);
|
| 957 |
+
|
| 958 |
setIsTyping(true);
|
| 959 |
try {
|
| 960 |
const docType = getCurrentDocTypeForChat();
|
|
|
|
| 964 |
language_preference: mapLanguagePref(language),
|
| 965 |
doc_type: docType,
|
| 966 |
learning_mode: "quiz",
|
| 967 |
+
course_id: backendCourseId,
|
| 968 |
+
} as any);
|
| 969 |
|
| 970 |
+
const refsObj = normalizeApiRefs((r as any).refs ?? (r as any).references);
|
| 971 |
+
const refsLegacy = refsToLegacyStrings(refsObj);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 972 |
|
| 973 |
const assistantMessage: Message = {
|
| 974 |
id: Date.now().toString(),
|
| 975 |
role: "assistant",
|
| 976 |
+
content: (r as any).reply || "",
|
| 977 |
timestamp: new Date(),
|
| 978 |
+
refs: refsObj.length ? refsObj : undefined,
|
| 979 |
+
references: refsLegacy.length ? refsLegacy : undefined,
|
| 980 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 981 |
showNextButton: false,
|
| 982 |
};
|
|
|
|
| 1006 |
// =========================
|
| 1007 |
// File Upload (FIXED)
|
| 1008 |
// =========================
|
|
|
|
| 1009 |
const handleFileUpload = async (input: File[] | FileList | null | undefined) => {
|
| 1010 |
const files = Array.isArray(input) ? input : input ? Array.from(input) : [];
|
| 1011 |
if (!files.length) return;
|
|
|
|
| 1016 |
|
| 1017 |
if (!user) return;
|
| 1018 |
|
| 1019 |
+
const backendCourseId = getBackendCourseId(currentCourseId);
|
| 1020 |
+
|
| 1021 |
for (const f of files) {
|
| 1022 |
const fp = `${f.name}::${f.size}::${f.lastModified}`;
|
| 1023 |
if (uploadedFingerprintsRef.current.has(fp)) continue;
|
|
|
|
| 1027 |
await apiUpload({
|
| 1028 |
user_id: user.email,
|
| 1029 |
doc_type: DOC_TYPE_MAP["other"] || "Other Course Document",
|
| 1030 |
+
course_id: backendCourseId,
|
| 1031 |
file: f,
|
| 1032 |
+
} as any);
|
| 1033 |
toast.success(`File uploaded: ${f.name}`);
|
| 1034 |
} catch (e: any) {
|
| 1035 |
toast.error(e?.message || `Upload failed: ${f.name}`);
|
|
|
|
| 1088 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
| 1089 |
uploadedFingerprintsRef.current.add(fp);
|
| 1090 |
|
| 1091 |
+
const backendCourseId = getBackendCourseId(currentCourseId);
|
| 1092 |
+
|
| 1093 |
try {
|
| 1094 |
await apiUpload({
|
| 1095 |
user_id: user.email,
|
| 1096 |
doc_type: DOC_TYPE_MAP[type] || "Other Course Document",
|
| 1097 |
+
course_id: backendCourseId,
|
| 1098 |
file: target,
|
| 1099 |
+
} as any);
|
| 1100 |
toast.success("File uploaded to backend");
|
| 1101 |
} catch (e: any) {
|
| 1102 |
toast.error(e?.message || "Upload failed");
|
|
|
|
| 1560 |
onReviewActivity={handleReviewActivity}
|
| 1561 |
currentUserId={user?.email}
|
| 1562 |
docType={"Syllabus"}
|
|
|
|
| 1563 |
onProfileBioUpdate={(bio) => updateUser({ bio })}
|
| 1564 |
/>
|
| 1565 |
</div>
|