SarahXia0405 commited on
Commit
bebf4a3
·
verified ·
1 Parent(s): 5072b2a

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +61 -37
web/src/components/ChatArea.tsx CHANGED
@@ -50,6 +50,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
50
  import { SmartReview } from "./SmartReview";
51
  import clareAvatar from "../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
52
 
 
 
53
  interface ChatAreaProps {
54
  messages: MessageType[];
55
  onSendMessage: (content: string) => void;
@@ -86,6 +88,9 @@ interface ChatAreaProps {
86
  onCourseChange?: (courseId: string) => void;
87
  availableCourses?: Array<{ id: string; name: string }>;
88
  showReviewBanner?: boolean;
 
 
 
89
  }
90
 
91
  interface PendingFile {
@@ -125,6 +130,7 @@ export function ChatArea({
125
  onCourseChange,
126
  availableCourses = [],
127
  showReviewBanner = false,
 
128
  }: ChatAreaProps) {
129
  const [input, setInput] = useState("");
130
  const [showScrollButton, setShowScrollButton] = useState(false);
@@ -240,6 +246,9 @@ export function ChatArea({
240
  e.preventDefault();
241
  if (!input.trim() || !isLoggedIn) return;
242
 
 
 
 
243
  onSendMessage(input);
244
  setInput("");
245
  };
@@ -270,6 +279,9 @@ export function ChatArea({
270
  weight: number;
271
  lastReviewed: string;
272
  }) => {
 
 
 
273
  const userMessage = `Please help me review: ${item.title}`;
274
  const reviewData = `REVIEW_TOPIC:${item.title}|${item.previousQuestion}|${item.memoryRetention}|${item.schedule}|${item.status}|${item.weight}|${item.lastReviewed}`;
275
  (window as any).__lastReviewData = reviewData;
@@ -277,6 +289,9 @@ export function ChatArea({
277
  };
278
 
279
  const handleReviewAll = () => {
 
 
 
280
  (window as any).__lastReviewData = "REVIEW_ALL";
281
  onSendMessage("Please help me review all topics that need attention.");
282
  };
@@ -593,7 +608,11 @@ export function ChatArea({
593
 
594
  if (isImage) {
595
  return (
596
- <div className="relative cursor-pointer w-16 h-16 flex-shrink-0" onClick={onPreview} style={{ width: 64, height: 64 }}>
 
 
 
 
597
  <div className="w-full h-full relative bg-card border border-border rounded-lg hover:border-primary/50 transition-colors">
598
  <div className="w-full h-full overflow-hidden rounded-lg absolute inset-0">
599
  {imageLoading ? (
@@ -729,11 +748,7 @@ export function ChatArea({
729
  );
730
  }
731
 
732
- return (
733
- <div className="whitespace-pre-wrap text-sm font-mono p-4 bg-muted rounded-lg max-h-[60vh] overflow-y-auto">
734
- {content}
735
- </div>
736
- );
737
  };
738
 
739
  // ✅ Reserve space for composer so last message is never hidden
@@ -784,12 +799,7 @@ export function ChatArea({
784
 
785
  {/* Chat Mode Tabs - Center */}
786
  <div className="absolute left-1/2 -translate-x-1/2 flex-shrink-0">
787
- <Tabs
788
- value={chatMode}
789
- onValueChange={(value) => onChatModeChange(value as ChatMode)}
790
- className="w-auto"
791
- orientation="horizontal"
792
- >
793
  <TabsList className="inline-flex h-8 items-center justify-center rounded-xl bg-muted p-1 text-muted-foreground">
794
  <TabsTrigger value="ask" className="w-[140px] px-3 text-sm">
795
  Ask
@@ -865,11 +875,7 @@ export function ChatArea({
865
  {/* =========================
866
  1) Scroll Container (ONLY this scrolls)
867
  ========================= */}
868
- <div
869
- ref={scrollContainerRef}
870
- className="flex-1 min-h-0 overflow-y-auto overscroll-contain"
871
- style={{ overscrollBehavior: "contain" }}
872
- >
873
  {/* Messages */}
874
  <div className="py-6" style={{ paddingBottom: bottomPad }}>
875
  <div className="w-full space-y-6 max-w-4xl mx-auto">
@@ -879,8 +885,7 @@ export function ChatArea({
879
  message={message}
880
  showSenderInfo={spaceType === "group"}
881
  isFirstGreeting={
882
- (message.id === "1" || message.id === "review-1" || message.id === "quiz-1") &&
883
- message.role === "assistant"
884
  }
885
  showNextButton={message.showNextButton && !isAppTyping}
886
  onNextQuestion={onNextQuestion}
@@ -933,10 +938,7 @@ export function ChatArea({
933
  2) Scroll-to-bottom button (positioned above composer)
934
  ========================= */}
935
  {showScrollButton && (
936
- <div
937
- className="absolute z-30 left-0 right-0 flex justify-center pointer-events-none"
938
- style={{ bottom: composerHeight + 16 }}
939
- >
940
  <Button
941
  variant="secondary"
942
  size="icon"
@@ -1013,21 +1015,30 @@ export function ChatArea({
1013
  </Button>
1014
  </DropdownMenuTrigger>
1015
  <DropdownMenuContent align="start" className="w-56">
1016
- <DropdownMenuItem onClick={() => onLearningModeChange("general")} className={learningMode === "general" ? "bg-accent" : ""}>
 
 
 
1017
  <div className="flex flex-col">
1018
  <span className="font-medium">General</span>
1019
  <span className="text-xs text-muted-foreground">Answer various questions (context required)</span>
1020
  </div>
1021
  </DropdownMenuItem>
1022
 
1023
- <DropdownMenuItem onClick={() => onLearningModeChange("concept")} className={learningMode === "concept" ? "bg-accent" : ""}>
 
 
 
1024
  <div className="flex flex-col">
1025
  <span className="font-medium">Concept Explainer</span>
1026
  <span className="text-xs text-muted-foreground">Get detailed explanations of concepts</span>
1027
  </div>
1028
  </DropdownMenuItem>
1029
 
1030
- <DropdownMenuItem onClick={() => onLearningModeChange("socratic")} className={learningMode === "socratic" ? "bg-accent" : ""}>
 
 
 
1031
  <div className="flex flex-col">
1032
  <span className="font-medium">Socratic Tutor</span>
1033
  <span className="text-xs text-muted-foreground">Learn through guided questions</span>
@@ -1041,14 +1052,20 @@ export function ChatArea({
1041
  </div>
1042
  </DropdownMenuItem>
1043
 
1044
- <DropdownMenuItem onClick={() => onLearningModeChange("assignment")} className={learningMode === "assignment" ? "bg-accent" : ""}>
 
 
 
1045
  <div className="flex flex-col">
1046
  <span className="font-medium">Assignment Helper</span>
1047
  <span className="text-xs text-muted-foreground">Get help with assignments</span>
1048
  </div>
1049
  </DropdownMenuItem>
1050
 
1051
- <DropdownMenuItem onClick={() => onLearningModeChange("summary")} className={learningMode === "summary" ? "bg-accent" : ""}>
 
 
 
1052
  <div className="flex flex-col">
1053
  <span className="font-medium">Quick Summary</span>
1054
  <span className="text-xs text-muted-foreground">Get concise summaries</span>
@@ -1091,7 +1108,9 @@ export function ChatArea({
1091
  : "Ask Clare anything about the course or drag files here..."
1092
  }
1093
  disabled={!isLoggedIn || (chatMode === "quiz" && !quizState.waitingForAnswer)}
1094
- className={`min-h-[80px] pl-4 pr-20 resize-none bg-background border-2 ${isDragging ? "border-primary border-dashed" : "border-border"}`}
 
 
1095
  />
1096
 
1097
  <div className="absolute bottom-2 right-2 flex gap-1">
@@ -1119,9 +1138,7 @@ export function ChatArea({
1119
  <AlertDialogContent>
1120
  <AlertDialogHeader>
1121
  <AlertDialogTitle>Start New Conversation</AlertDialogTitle>
1122
- <AlertDialogDescription>
1123
- Would you like to save the current chat before starting a new conversation?
1124
- </AlertDialogDescription>
1125
 
1126
  <Button
1127
  variant="ghost"
@@ -1173,7 +1190,13 @@ export function ChatArea({
1173
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1174
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1175
  <span className="text-sm font-medium">Preview</span>
1176
- <Button variant="outline" size="sm" className="h-7 px-2 text-xs gap-1.5" onClick={handleCopyPreview} title="Copy preview">
 
 
 
 
 
 
1177
  <Copy className="h-3 w-3" />
1178
  Copy
1179
  </Button>
@@ -1248,9 +1271,7 @@ export function ChatArea({
1248
  ))}
1249
  </SelectContent>
1250
  </Select>
1251
- <p className="text-xs text-muted-foreground">
1252
- Sends this conversation to the selected workspace&apos;s Saved Files.
1253
- </p>
1254
  <Button onClick={handleShareSendToWorkspace} className="w-full">
1255
  Send
1256
  </Button>
@@ -1326,7 +1347,10 @@ export function ChatArea({
1326
 
1327
  <div className="space-y-1">
1328
  <label className="text-xs text-muted-foreground">File Type</label>
1329
- <Select value={pendingFile.type} onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}>
 
 
 
1330
  <SelectTrigger className="h-8 text-xs">
1331
  <SelectValue />
1332
  </SelectTrigger>
 
50
  import { SmartReview } from "./SmartReview";
51
  import clareAvatar from "../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
52
 
53
+ type ReviewEventType = "send_message" | "review_topic" | "review_all";
54
+
55
  interface ChatAreaProps {
56
  messages: MessageType[];
57
  onSendMessage: (content: string) => void;
 
88
  onCourseChange?: (courseId: string) => void;
89
  availableCourses?: Array<{ id: string; name: string }>;
90
  showReviewBanner?: boolean;
91
+
92
+ // ✅ NEW: for Review star brightness (daily)
93
+ onReviewActivity?: (event: ReviewEventType) => void;
94
  }
95
 
96
  interface PendingFile {
 
130
  onCourseChange,
131
  availableCourses = [],
132
  showReviewBanner = false,
133
+ onReviewActivity,
134
  }: ChatAreaProps) {
135
  const [input, setInput] = useState("");
136
  const [showScrollButton, setShowScrollButton] = useState(false);
 
246
  e.preventDefault();
247
  if (!input.trim() || !isLoggedIn) return;
248
 
249
+ // ✅ Review activity: user sent a message in Review tab
250
+ if (chatMode === "review") onReviewActivity?.("send_message");
251
+
252
  onSendMessage(input);
253
  setInput("");
254
  };
 
279
  weight: number;
280
  lastReviewed: string;
281
  }) => {
282
+ // ✅ Review activity: clicked Review this topic
283
+ onReviewActivity?.("review_topic");
284
+
285
  const userMessage = `Please help me review: ${item.title}`;
286
  const reviewData = `REVIEW_TOPIC:${item.title}|${item.previousQuestion}|${item.memoryRetention}|${item.schedule}|${item.status}|${item.weight}|${item.lastReviewed}`;
287
  (window as any).__lastReviewData = reviewData;
 
289
  };
290
 
291
  const handleReviewAll = () => {
292
+ // ✅ Review activity: clicked Review all
293
+ onReviewActivity?.("review_all");
294
+
295
  (window as any).__lastReviewData = "REVIEW_ALL";
296
  onSendMessage("Please help me review all topics that need attention.");
297
  };
 
608
 
609
  if (isImage) {
610
  return (
611
+ <div
612
+ className="relative cursor-pointer w-16 h-16 flex-shrink-0"
613
+ onClick={onPreview}
614
+ style={{ width: 64, height: 64 }}
615
+ >
616
  <div className="w-full h-full relative bg-card border border-border rounded-lg hover:border-primary/50 transition-colors">
617
  <div className="w-full h-full overflow-hidden rounded-lg absolute inset-0">
618
  {imageLoading ? (
 
748
  );
749
  }
750
 
751
+ return <div className="whitespace-pre-wrap text-sm font-mono p-4 bg-muted rounded-lg max-h-[60vh] overflow-y-auto">{content}</div>;
 
 
 
 
752
  };
753
 
754
  // ✅ Reserve space for composer so last message is never hidden
 
799
 
800
  {/* Chat Mode Tabs - Center */}
801
  <div className="absolute left-1/2 -translate-x-1/2 flex-shrink-0">
802
+ <Tabs value={chatMode} onValueChange={(value) => onChatModeChange(value as ChatMode)} className="w-auto" orientation="horizontal">
 
 
 
 
 
803
  <TabsList className="inline-flex h-8 items-center justify-center rounded-xl bg-muted p-1 text-muted-foreground">
804
  <TabsTrigger value="ask" className="w-[140px] px-3 text-sm">
805
  Ask
 
875
  {/* =========================
876
  1) Scroll Container (ONLY this scrolls)
877
  ========================= */}
878
+ <div ref={scrollContainerRef} className="flex-1 min-h-0 overflow-y-auto overscroll-contain" style={{ overscrollBehavior: "contain" }}>
 
 
 
 
879
  {/* Messages */}
880
  <div className="py-6" style={{ paddingBottom: bottomPad }}>
881
  <div className="w-full space-y-6 max-w-4xl mx-auto">
 
885
  message={message}
886
  showSenderInfo={spaceType === "group"}
887
  isFirstGreeting={
888
+ (message.id === "1" || message.id === "review-1" || message.id === "quiz-1") && message.role === "assistant"
 
889
  }
890
  showNextButton={message.showNextButton && !isAppTyping}
891
  onNextQuestion={onNextQuestion}
 
938
  2) Scroll-to-bottom button (positioned above composer)
939
  ========================= */}
940
  {showScrollButton && (
941
+ <div className="absolute z-30 left-0 right-0 flex justify-center pointer-events-none" style={{ bottom: composerHeight + 16 }}>
 
 
 
942
  <Button
943
  variant="secondary"
944
  size="icon"
 
1015
  </Button>
1016
  </DropdownMenuTrigger>
1017
  <DropdownMenuContent align="start" className="w-56">
1018
+ <DropdownMenuItem
1019
+ onClick={() => onLearningModeChange("general")}
1020
+ className={learningMode === "general" ? "bg-accent" : ""}
1021
+ >
1022
  <div className="flex flex-col">
1023
  <span className="font-medium">General</span>
1024
  <span className="text-xs text-muted-foreground">Answer various questions (context required)</span>
1025
  </div>
1026
  </DropdownMenuItem>
1027
 
1028
+ <DropdownMenuItem
1029
+ onClick={() => onLearningModeChange("concept")}
1030
+ className={learningMode === "concept" ? "bg-accent" : ""}
1031
+ >
1032
  <div className="flex flex-col">
1033
  <span className="font-medium">Concept Explainer</span>
1034
  <span className="text-xs text-muted-foreground">Get detailed explanations of concepts</span>
1035
  </div>
1036
  </DropdownMenuItem>
1037
 
1038
+ <DropdownMenuItem
1039
+ onClick={() => onLearningModeChange("socratic")}
1040
+ className={learningMode === "socratic" ? "bg-accent" : ""}
1041
+ >
1042
  <div className="flex flex-col">
1043
  <span className="font-medium">Socratic Tutor</span>
1044
  <span className="text-xs text-muted-foreground">Learn through guided questions</span>
 
1052
  </div>
1053
  </DropdownMenuItem>
1054
 
1055
+ <DropdownMenuItem
1056
+ onClick={() => onLearningModeChange("assignment")}
1057
+ className={learningMode === "assignment" ? "bg-accent" : ""}
1058
+ >
1059
  <div className="flex flex-col">
1060
  <span className="font-medium">Assignment Helper</span>
1061
  <span className="text-xs text-muted-foreground">Get help with assignments</span>
1062
  </div>
1063
  </DropdownMenuItem>
1064
 
1065
+ <DropdownMenuItem
1066
+ onClick={() => onLearningModeChange("summary")}
1067
+ className={learningMode === "summary" ? "bg-accent" : ""}
1068
+ >
1069
  <div className="flex flex-col">
1070
  <span className="font-medium">Quick Summary</span>
1071
  <span className="text-xs text-muted-foreground">Get concise summaries</span>
 
1108
  : "Ask Clare anything about the course or drag files here..."
1109
  }
1110
  disabled={!isLoggedIn || (chatMode === "quiz" && !quizState.waitingForAnswer)}
1111
+ className={`min-h-[80px] pl-4 pr-20 resize-none bg-background border-2 ${
1112
+ isDragging ? "border-primary border-dashed" : "border-border"
1113
+ }`}
1114
  />
1115
 
1116
  <div className="absolute bottom-2 right-2 flex gap-1">
 
1138
  <AlertDialogContent>
1139
  <AlertDialogHeader>
1140
  <AlertDialogTitle>Start New Conversation</AlertDialogTitle>
1141
+ <AlertDialogDescription>Would you like to save the current chat before starting a new conversation?</AlertDialogDescription>
 
 
1142
 
1143
  <Button
1144
  variant="ghost"
 
1190
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1191
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1192
  <span className="text-sm font-medium">Preview</span>
1193
+ <Button
1194
+ variant="outline"
1195
+ size="sm"
1196
+ className="h-7 px-2 text-xs gap-1.5"
1197
+ onClick={handleCopyPreview}
1198
+ title="Copy preview"
1199
+ >
1200
  <Copy className="h-3 w-3" />
1201
  Copy
1202
  </Button>
 
1271
  ))}
1272
  </SelectContent>
1273
  </Select>
1274
+ <p className="text-xs text-muted-foreground">Sends this conversation to the selected workspace&apos;s Saved Files.</p>
 
 
1275
  <Button onClick={handleShareSendToWorkspace} className="w-full">
1276
  Send
1277
  </Button>
 
1347
 
1348
  <div className="space-y-1">
1349
  <label className="text-xs text-muted-foreground">File Type</label>
1350
+ <Select
1351
+ value={pendingFile.type}
1352
+ onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}
1353
+ >
1354
  <SelectTrigger className="h-8 text-xs">
1355
  <SelectValue />
1356
  </SelectTrigger>