Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +46 -26
web/src/App.tsx
CHANGED
|
@@ -611,42 +611,62 @@ function App() {
|
|
| 611 |
return "Syllabus";
|
| 612 |
};
|
| 613 |
|
| 614 |
-
const handleSendMessage = async (content: string) => {
|
| 615 |
-
if (!content.trim() || !user) return;
|
| 616 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
const sender: GroupMember = {
|
| 618 |
id: user.email,
|
| 619 |
name: user.name,
|
| 620 |
email: user.email,
|
| 621 |
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(user.email)}`,
|
| 622 |
};
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
const userMessage: Message = {
|
| 625 |
id: Date.now().toString(),
|
| 626 |
role: "user",
|
| 627 |
-
content,
|
| 628 |
timestamp: new Date(),
|
| 629 |
sender,
|
| 630 |
};
|
| 631 |
-
|
| 632 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, userMessage]);
|
| 633 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, userMessage]);
|
| 634 |
else setQuizMessages((prev) => [...prev, userMessage]);
|
| 635 |
-
|
| 636 |
if (chatMode === "quiz") {
|
| 637 |
setIsTyping(true);
|
| 638 |
-
|
| 639 |
try {
|
| 640 |
const docType = getCurrentDocTypeForChat();
|
| 641 |
-
|
| 642 |
const r = await apiChat({
|
| 643 |
user_id: user.email,
|
| 644 |
-
message:
|
| 645 |
learning_mode: "quiz",
|
| 646 |
language_preference: mapLanguagePref(language),
|
| 647 |
doc_type: docType,
|
| 648 |
});
|
| 649 |
-
|
| 650 |
const refs = (r.refs || [])
|
| 651 |
.map((x) => {
|
| 652 |
const a = x?.source_file ? String(x.source_file) : "";
|
|
@@ -655,7 +675,7 @@ function App() {
|
|
| 655 |
return s || null;
|
| 656 |
})
|
| 657 |
.filter(Boolean) as string[];
|
| 658 |
-
|
| 659 |
const assistantMessage: Message = {
|
| 660 |
id: (Date.now() + 1).toString(),
|
| 661 |
role: "assistant",
|
|
@@ -665,9 +685,9 @@ function App() {
|
|
| 665 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 666 |
showNextButton: false,
|
| 667 |
};
|
| 668 |
-
|
| 669 |
setIsTyping(false);
|
| 670 |
-
|
| 671 |
setTimeout(() => {
|
| 672 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 673 |
setQuizState((prev) => ({ ...prev, waitingForAnswer: true, showNextButton: false }));
|
|
@@ -675,7 +695,7 @@ function App() {
|
|
| 675 |
} catch (e: any) {
|
| 676 |
setIsTyping(false);
|
| 677 |
toast.error(e?.message || "Quiz failed");
|
| 678 |
-
|
| 679 |
const assistantMessage: Message = {
|
| 680 |
id: (Date.now() + 1).toString(),
|
| 681 |
role: "assistant",
|
|
@@ -683,27 +703,27 @@ function App() {
|
|
| 683 |
timestamp: new Date(),
|
| 684 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 685 |
};
|
| 686 |
-
|
| 687 |
setTimeout(() => {
|
| 688 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 689 |
}, 50);
|
| 690 |
}
|
| 691 |
-
|
| 692 |
return;
|
| 693 |
}
|
| 694 |
-
|
| 695 |
setIsTyping(true);
|
| 696 |
try {
|
| 697 |
const docType = getCurrentDocTypeForChat();
|
| 698 |
-
|
| 699 |
const r = await apiChat({
|
| 700 |
user_id: user.email,
|
| 701 |
-
message:
|
| 702 |
learning_mode: learningMode,
|
| 703 |
language_preference: mapLanguagePref(language),
|
| 704 |
doc_type: docType,
|
| 705 |
});
|
| 706 |
-
|
| 707 |
const refs = (r.refs || [])
|
| 708 |
.map((x) => {
|
| 709 |
const a = x?.source_file ? String(x.source_file) : "";
|
|
@@ -712,7 +732,7 @@ function App() {
|
|
| 712 |
return s || null;
|
| 713 |
})
|
| 714 |
.filter(Boolean) as string[];
|
| 715 |
-
|
| 716 |
const assistantMessage: Message = {
|
| 717 |
id: (Date.now() + 1).toString(),
|
| 718 |
role: "assistant",
|
|
@@ -721,14 +741,14 @@ function App() {
|
|
| 721 |
references: refs.length ? refs : undefined,
|
| 722 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 723 |
};
|
| 724 |
-
|
| 725 |
setIsTyping(false);
|
| 726 |
-
|
| 727 |
setTimeout(() => {
|
| 728 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, assistantMessage]);
|
| 729 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, assistantMessage]);
|
| 730 |
}, 50);
|
| 731 |
-
|
| 732 |
try {
|
| 733 |
const ml = await apiMemoryline(user.email);
|
| 734 |
setMemoryProgress(Math.round((ml.progress_pct ?? 0) * 100));
|
|
@@ -738,7 +758,7 @@ function App() {
|
|
| 738 |
} catch (e: any) {
|
| 739 |
setIsTyping(false);
|
| 740 |
toast.error(e?.message || "Chat failed");
|
| 741 |
-
|
| 742 |
const assistantMessage: Message = {
|
| 743 |
id: (Date.now() + 1).toString(),
|
| 744 |
role: "assistant",
|
|
@@ -746,7 +766,7 @@ function App() {
|
|
| 746 |
timestamp: new Date(),
|
| 747 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 748 |
};
|
| 749 |
-
|
| 750 |
setTimeout(() => {
|
| 751 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, assistantMessage]);
|
| 752 |
if (chatMode === "review") setReviewMessages((prev) => [...prev, assistantMessage]);
|
|
|
|
| 611 |
return "Syllabus";
|
| 612 |
};
|
| 613 |
|
|
|
|
|
|
|
| 614 |
|
| 615 |
+
const handleSendMessage = async (content: string) => {
|
| 616 |
+
if (!user) return;
|
| 617 |
+
|
| 618 |
+
const hasText = !!content.trim();
|
| 619 |
+
const hasFiles = uploadedFiles.length > 0;
|
| 620 |
+
|
| 621 |
+
// ✅ 允许“只发文件不发文字”
|
| 622 |
+
if (!hasText && !hasFiles) return;
|
| 623 |
+
|
| 624 |
+
// ✅ 如果用户没写字,但上传了文件:构造一条后端可用的 message
|
| 625 |
+
const fileNames = hasFiles ? uploadedFiles.map((f) => f.file.name) : [];
|
| 626 |
+
const fileLine = fileNames.length ? `Uploaded files: ${fileNames.join(", ")}` : "";
|
| 627 |
+
|
| 628 |
+
const effectiveContent = hasText
|
| 629 |
+
? content
|
| 630 |
+
: `I've uploaded file(s). Please read them and help me based on their content.\n${fileLine}`.trim();
|
| 631 |
+
|
| 632 |
const sender: GroupMember = {
|
| 633 |
id: user.email,
|
| 634 |
name: user.name,
|
| 635 |
email: user.email,
|
| 636 |
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(user.email)}`,
|
| 637 |
};
|
| 638 |
+
|
| 639 |
+
// ✅ 用户消息在 UI 里也要可见(否则会出现空白气泡)
|
| 640 |
+
const userVisibleContent = hasText
|
| 641 |
+
? content
|
| 642 |
+
: `📎 Sent ${fileNames.length} file(s)\n${fileNames.map((n) => `- ${n}`).join("\n")}`;
|
| 643 |
+
|
| 644 |
const userMessage: Message = {
|
| 645 |
id: Date.now().toString(),
|
| 646 |
role: "user",
|
| 647 |
+
content: userVisibleContent,
|
| 648 |
timestamp: new Date(),
|
| 649 |
sender,
|
| 650 |
};
|
| 651 |
+
|
| 652 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, userMessage]);
|
| 653 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, userMessage]);
|
| 654 |
else setQuizMessages((prev) => [...prev, userMessage]);
|
| 655 |
+
|
| 656 |
if (chatMode === "quiz") {
|
| 657 |
setIsTyping(true);
|
| 658 |
+
|
| 659 |
try {
|
| 660 |
const docType = getCurrentDocTypeForChat();
|
| 661 |
+
|
| 662 |
const r = await apiChat({
|
| 663 |
user_id: user.email,
|
| 664 |
+
message: effectiveContent,
|
| 665 |
learning_mode: "quiz",
|
| 666 |
language_preference: mapLanguagePref(language),
|
| 667 |
doc_type: docType,
|
| 668 |
});
|
| 669 |
+
|
| 670 |
const refs = (r.refs || [])
|
| 671 |
.map((x) => {
|
| 672 |
const a = x?.source_file ? String(x.source_file) : "";
|
|
|
|
| 675 |
return s || null;
|
| 676 |
})
|
| 677 |
.filter(Boolean) as string[];
|
| 678 |
+
|
| 679 |
const assistantMessage: Message = {
|
| 680 |
id: (Date.now() + 1).toString(),
|
| 681 |
role: "assistant",
|
|
|
|
| 685 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 686 |
showNextButton: false,
|
| 687 |
};
|
| 688 |
+
|
| 689 |
setIsTyping(false);
|
| 690 |
+
|
| 691 |
setTimeout(() => {
|
| 692 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 693 |
setQuizState((prev) => ({ ...prev, waitingForAnswer: true, showNextButton: false }));
|
|
|
|
| 695 |
} catch (e: any) {
|
| 696 |
setIsTyping(false);
|
| 697 |
toast.error(e?.message || "Quiz failed");
|
| 698 |
+
|
| 699 |
const assistantMessage: Message = {
|
| 700 |
id: (Date.now() + 1).toString(),
|
| 701 |
role: "assistant",
|
|
|
|
| 703 |
timestamp: new Date(),
|
| 704 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 705 |
};
|
| 706 |
+
|
| 707 |
setTimeout(() => {
|
| 708 |
setQuizMessages((prev) => [...prev, assistantMessage]);
|
| 709 |
}, 50);
|
| 710 |
}
|
| 711 |
+
|
| 712 |
return;
|
| 713 |
}
|
| 714 |
+
|
| 715 |
setIsTyping(true);
|
| 716 |
try {
|
| 717 |
const docType = getCurrentDocTypeForChat();
|
| 718 |
+
|
| 719 |
const r = await apiChat({
|
| 720 |
user_id: user.email,
|
| 721 |
+
message: effectiveContent,
|
| 722 |
learning_mode: learningMode,
|
| 723 |
language_preference: mapLanguagePref(language),
|
| 724 |
doc_type: docType,
|
| 725 |
});
|
| 726 |
+
|
| 727 |
const refs = (r.refs || [])
|
| 728 |
.map((x) => {
|
| 729 |
const a = x?.source_file ? String(x.source_file) : "";
|
|
|
|
| 732 |
return s || null;
|
| 733 |
})
|
| 734 |
.filter(Boolean) as string[];
|
| 735 |
+
|
| 736 |
const assistantMessage: Message = {
|
| 737 |
id: (Date.now() + 1).toString(),
|
| 738 |
role: "assistant",
|
|
|
|
| 741 |
references: refs.length ? refs : undefined,
|
| 742 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 743 |
};
|
| 744 |
+
|
| 745 |
setIsTyping(false);
|
| 746 |
+
|
| 747 |
setTimeout(() => {
|
| 748 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, assistantMessage]);
|
| 749 |
else if (chatMode === "review") setReviewMessages((prev) => [...prev, assistantMessage]);
|
| 750 |
}, 50);
|
| 751 |
+
|
| 752 |
try {
|
| 753 |
const ml = await apiMemoryline(user.email);
|
| 754 |
setMemoryProgress(Math.round((ml.progress_pct ?? 0) * 100));
|
|
|
|
| 758 |
} catch (e: any) {
|
| 759 |
setIsTyping(false);
|
| 760 |
toast.error(e?.message || "Chat failed");
|
| 761 |
+
|
| 762 |
const assistantMessage: Message = {
|
| 763 |
id: (Date.now() + 1).toString(),
|
| 764 |
role: "assistant",
|
|
|
|
| 766 |
timestamp: new Date(),
|
| 767 |
sender: spaceType === "group" ? groupMembers.find((m) => m.isAI) : undefined,
|
| 768 |
};
|
| 769 |
+
|
| 770 |
setTimeout(() => {
|
| 771 |
if (chatMode === "ask") setAskMessages((prev) => [...prev, assistantMessage]);
|
| 772 |
if (chatMode === "review") setReviewMessages((prev) => [...prev, assistantMessage]);
|