Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +175 -39
web/src/App.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import { toast } from "sonner";
|
|
| 14 |
import { LeftSidebar } from "./components/sidebar/LeftSidebar";
|
| 15 |
|
| 16 |
// backend API bindings
|
| 17 |
-
import { apiChat, apiUpload, apiMemoryline } from "./lib/api";
|
| 18 |
|
| 19 |
// NEW: review-star logic
|
| 20 |
import {
|
|
@@ -645,35 +645,70 @@ function App() {
|
|
| 645 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, userMessage]);
|
| 646 |
else setQuizMessages((prev) => [...prev, userMessage]);
|
| 647 |
|
|
|
|
| 648 |
if (chatMode === "quiz") {
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
|
|
|
|
|
|
|
| 653 |
setTimeout(() => {
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
},
|
| 673 |
}
|
|
|
|
| 674 |
return;
|
| 675 |
}
|
| 676 |
|
|
|
|
| 677 |
setIsTyping(true);
|
| 678 |
try {
|
| 679 |
const docType = getCurrentDocTypeForChat();
|
|
@@ -736,27 +771,60 @@ function App() {
|
|
| 736 |
}
|
| 737 |
};
|
| 738 |
|
| 739 |
-
const handleNextQuestion = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
setIsTyping(true);
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
const assistantMessage: Message = {
|
| 750 |
-
id: Date.now().toString(),
|
| 751 |
role: "assistant",
|
| 752 |
-
content:
|
| 753 |
timestamp: new Date(),
|
|
|
|
| 754 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 755 |
-
|
| 756 |
};
|
| 757 |
-
|
| 758 |
setIsTyping(false);
|
| 759 |
-
|
| 760 |
setTimeout(() => {
|
| 761 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 762 |
setQuizState((prev) => ({
|
|
@@ -766,10 +834,78 @@ function App() {
|
|
| 766 |
showNextButton: false,
|
| 767 |
}));
|
| 768 |
}, 50);
|
| 769 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
};
|
| 771 |
|
| 772 |
-
const handleStartQuiz = () => handleNextQuestion();
|
| 773 |
|
| 774 |
// =========================
|
| 775 |
// ✅ File Upload (FIXED)
|
|
|
|
| 14 |
import { LeftSidebar } from "./components/sidebar/LeftSidebar";
|
| 15 |
|
| 16 |
// backend API bindings
|
| 17 |
+
import { apiChat, apiUpload, apiMemoryline, apiQuizStart } from "./lib/api";
|
| 18 |
|
| 19 |
// NEW: review-star logic
|
| 20 |
import {
|
|
|
|
| 645 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, userMessage]);
|
| 646 |
else setQuizMessages((prev) => [...prev, userMessage]);
|
| 647 |
|
| 648 |
+
|
| 649 |
if (chatMode === "quiz") {
|
| 650 |
+
// Quiz mode: always route to backend (grading + next question handled server-side)
|
| 651 |
+
setIsTyping(true);
|
| 652 |
+
|
| 653 |
+
try {
|
| 654 |
+
const docType = getCurrentDocTypeForChat();
|
| 655 |
+
|
| 656 |
+
const r = await apiChat({
|
| 657 |
+
user_id: user.email,
|
| 658 |
+
message: content,
|
| 659 |
+
learning_mode: "quiz",
|
| 660 |
+
language_preference: mapLanguagePref(language),
|
| 661 |
+
doc_type: docType,
|
| 662 |
+
});
|
| 663 |
+
|
| 664 |
+
const refs = (r.refs || [])
|
| 665 |
+
.map((x) => {
|
| 666 |
+
const a = x?.source_file ? String(x.source_file) : "";
|
| 667 |
+
const b = x?.section ? String(x.section) : "";
|
| 668 |
+
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 669 |
+
return s || null;
|
| 670 |
+
})
|
| 671 |
+
.filter(Boolean) as string[];
|
| 672 |
+
|
| 673 |
+
const assistantMessage: Message = {
|
| 674 |
+
id: (Date.now() + 1).toString(),
|
| 675 |
+
role: "assistant",
|
| 676 |
+
content: r.reply || "",
|
| 677 |
+
timestamp: new Date(),
|
| 678 |
+
references: refs.length ? refs : undefined,
|
| 679 |
+
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 680 |
+
// Quiz flow is driven by backend, so don't force a local "Next" unless you want it
|
| 681 |
+
showNextButton: false,
|
| 682 |
+
};
|
| 683 |
|
| 684 |
+
setIsTyping(false);
|
| 685 |
+
|
| 686 |
setTimeout(() => {
|
| 687 |
+
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 688 |
+
// In quiz, backend typically asks the next prompt; keep waitingForAnswer true
|
| 689 |
+
setQuizState((prev) => ({ ...prev, waitingForAnswer: true, showNextButton: false }));
|
| 690 |
+
}, 50);
|
| 691 |
+
} catch (e: any) {
|
| 692 |
+
setIsTyping(false);
|
| 693 |
+
toast.error(e?.message || "Quiz failed");
|
| 694 |
+
|
| 695 |
+
const assistantMessage: Message = {
|
| 696 |
+
id: (Date.now() + 1).toString(),
|
| 697 |
+
role: "assistant",
|
| 698 |
+
content: "Sorry — quiz request failed. Please try again.",
|
| 699 |
+
timestamp: new Date(),
|
| 700 |
+
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 701 |
+
};
|
| 702 |
+
|
| 703 |
+
setTimeout(() => {
|
| 704 |
+
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 705 |
+
}, 50);
|
| 706 |
}
|
| 707 |
+
|
| 708 |
return;
|
| 709 |
}
|
| 710 |
|
| 711 |
+
|
| 712 |
setIsTyping(true);
|
| 713 |
try {
|
| 714 |
const docType = getCurrentDocTypeForChat();
|
|
|
|
| 771 |
}
|
| 772 |
};
|
| 773 |
|
| 774 |
+
const handleNextQuestion = async () => {
|
| 775 |
+
if (!user) return;
|
| 776 |
+
|
| 777 |
+
const prompt = "Please give me another question of the same quiz style.";
|
| 778 |
+
const sender: GroupMember = {
|
| 779 |
+
id: user.email,
|
| 780 |
+
name: user.name,
|
| 781 |
+
email: user.email,
|
| 782 |
+
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(user.email)}`,
|
| 783 |
+
};
|
| 784 |
+
|
| 785 |
+
// record the "next" action as a user turn (keeps server history consistent)
|
| 786 |
+
const userMessage: Message = {
|
| 787 |
+
id: Date.now().toString(),
|
| 788 |
+
role: "user",
|
| 789 |
+
content: prompt,
|
| 790 |
+
timestamp: new Date(),
|
| 791 |
+
sender,
|
| 792 |
+
};
|
| 793 |
+
|
| 794 |
+
setQuizMessages((prev) => [...prev, userMessage]);
|
| 795 |
setIsTyping(true);
|
| 796 |
+
|
| 797 |
+
try {
|
| 798 |
+
const docType = getCurrentDocTypeForChat();
|
| 799 |
+
const r = await apiChat({
|
| 800 |
+
user_id: user.email,
|
| 801 |
+
message: prompt,
|
| 802 |
+
learning_mode: "quiz",
|
| 803 |
+
language_preference: mapLanguagePref(language),
|
| 804 |
+
doc_type: docType,
|
| 805 |
+
});
|
| 806 |
+
|
| 807 |
+
const refs = (r.refs || [])
|
| 808 |
+
.map((x) => {
|
| 809 |
+
const a = x?.source_file ? String(x.source_file) : "";
|
| 810 |
+
const b = x?.section ? String(x.section) : "";
|
| 811 |
+
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 812 |
+
return s || null;
|
| 813 |
+
})
|
| 814 |
+
.filter(Boolean) as string[];
|
| 815 |
+
|
| 816 |
const assistantMessage: Message = {
|
| 817 |
+
id: (Date.now() + 1).toString(),
|
| 818 |
role: "assistant",
|
| 819 |
+
content: r.reply || "",
|
| 820 |
timestamp: new Date(),
|
| 821 |
+
references: refs.length ? refs : undefined,
|
| 822 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 823 |
+
showNextButton: false,
|
| 824 |
};
|
| 825 |
+
|
| 826 |
setIsTyping(false);
|
| 827 |
+
|
| 828 |
setTimeout(() => {
|
| 829 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 830 |
setQuizState((prev) => ({
|
|
|
|
| 834 |
showNextButton: false,
|
| 835 |
}));
|
| 836 |
}, 50);
|
| 837 |
+
} catch (e: any) {
|
| 838 |
+
setIsTyping(false);
|
| 839 |
+
toast.error(e?.message || "Quiz failed");
|
| 840 |
+
|
| 841 |
+
const assistantMessage: Message = {
|
| 842 |
+
id: (Date.now() + 1).toString(),
|
| 843 |
+
role: "assistant",
|
| 844 |
+
content: "Sorry — quiz request failed. Please try again.",
|
| 845 |
+
timestamp: new Date(),
|
| 846 |
+
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 847 |
+
};
|
| 848 |
+
|
| 849 |
+
setTimeout(() => setQuizMessages((prev) => [...prev, assistantMessage]), 50);
|
| 850 |
+
}
|
| 851 |
+
};
|
| 852 |
+
|
| 853 |
+
const handleStartQuiz = async () => {
|
| 854 |
+
if (!user) return;
|
| 855 |
+
|
| 856 |
+
setIsTyping(true);
|
| 857 |
+
try {
|
| 858 |
+
const docType = getCurrentDocTypeForChat();
|
| 859 |
+
|
| 860 |
+
const r = await apiQuizStart({
|
| 861 |
+
user_id: user.email,
|
| 862 |
+
language_preference: mapLanguagePref(language),
|
| 863 |
+
doc_type: docType,
|
| 864 |
+
learning_mode: "quiz",
|
| 865 |
+
});
|
| 866 |
+
|
| 867 |
+
const refs = (r.refs || [])
|
| 868 |
+
.map((x) => {
|
| 869 |
+
const a = x?.source_file ? String(x.source_file) : "";
|
| 870 |
+
const b = x?.section ? String(x.section) : "";
|
| 871 |
+
const s = `${a}${a && b ? " — " : ""}${b}`.trim();
|
| 872 |
+
return s || null;
|
| 873 |
+
})
|
| 874 |
+
.filter(Boolean) as string[];
|
| 875 |
+
|
| 876 |
+
const assistantMessage: Message = {
|
| 877 |
+
id: Date.now().toString(),
|
| 878 |
+
role: "assistant",
|
| 879 |
+
content: r.reply || "",
|
| 880 |
+
timestamp: new Date(),
|
| 881 |
+
references: refs.length ? refs : undefined,
|
| 882 |
+
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 883 |
+
showNextButton: false,
|
| 884 |
+
};
|
| 885 |
+
|
| 886 |
+
setIsTyping(false);
|
| 887 |
+
|
| 888 |
+
setTimeout(() => {
|
| 889 |
+
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 890 |
+
// backend will ask: "choose 1 or 2" first; user should answer next
|
| 891 |
+
setQuizState({ currentQuestion: 0, waitingForAnswer: true, showNextButton: false });
|
| 892 |
+
}, 50);
|
| 893 |
+
} catch (e: any) {
|
| 894 |
+
setIsTyping(false);
|
| 895 |
+
toast.error(e?.message || "Start quiz failed");
|
| 896 |
+
|
| 897 |
+
const assistantMessage: Message = {
|
| 898 |
+
id: Date.now().toString(),
|
| 899 |
+
role: "assistant",
|
| 900 |
+
content: "Sorry — could not start the quiz. Please try again.",
|
| 901 |
+
timestamp: new Date(),
|
| 902 |
+
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 903 |
+
};
|
| 904 |
+
|
| 905 |
+
setTimeout(() => setQuizMessages((prev) => [...prev, assistantMessage]), 50);
|
| 906 |
+
}
|
| 907 |
};
|
| 908 |
|
|
|
|
| 909 |
|
| 910 |
// =========================
|
| 911 |
// ✅ File Upload (FIXED)
|