Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +73 -25
web/src/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
// web/src/App.tsx
|
| 2 |
import React, { useState, useEffect, useRef, useMemo } from "react";
|
| 3 |
import { Header } from "./components/Header";
|
| 4 |
-
import { LeftSidebar } from "./components/LeftSidebar";
|
| 5 |
import { ChatArea } from "./components/ChatArea";
|
| 6 |
import { LoginScreen } from "./components/LoginScreen";
|
| 7 |
import { ProfileEditor } from "./components/ProfileEditor";
|
|
@@ -11,10 +11,7 @@ import { X, ChevronLeft, ChevronRight } from "lucide-react";
|
|
| 11 |
import { Button } from "./components/ui/button";
|
| 12 |
import { Toaster } from "./components/ui/sonner";
|
| 13 |
import { toast } from "sonner";
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
// import { COURSE_DIRECTORY } from "./lib/courseDirectory";
|
| 17 |
-
|
| 18 |
|
| 19 |
// backend API bindings
|
| 20 |
import { apiChat, apiUpload, apiMemoryline } from "./lib/api";
|
|
@@ -394,6 +391,64 @@ function App() {
|
|
| 394 |
|
| 395 |
const spaceType: SpaceType = currentWorkspace?.type || "individual";
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
useEffect(() => {
|
| 398 |
if (!currentWorkspace) return;
|
| 399 |
|
|
@@ -674,9 +729,8 @@ function App() {
|
|
| 674 |
const newFiles: UploadedFile[] = files.map((file) => ({ file, type: "other" as FileType }));
|
| 675 |
setUploadedFiles((prev) => [...prev, ...newFiles]);
|
| 676 |
};
|
| 677 |
-
|
| 678 |
|
| 679 |
-
const handleRemoveFile = (file: File) => setUploadedFiles(prev => prev.filter(u => u.file !== file));
|
| 680 |
|
| 681 |
// ✅ CRITICAL FIX:
|
| 682 |
// - use functional setState to avoid stale closure
|
|
@@ -723,11 +777,7 @@ function App() {
|
|
| 723 |
|
| 724 |
return chat.messages.every((savedMsg, idx) => {
|
| 725 |
const currentMsg = messages[idx];
|
| 726 |
-
return
|
| 727 |
-
savedMsg.id === currentMsg.id &&
|
| 728 |
-
savedMsg.role === currentMsg.role &&
|
| 729 |
-
savedMsg.content === currentMsg.content
|
| 730 |
-
);
|
| 731 |
});
|
| 732 |
}) || null
|
| 733 |
);
|
|
@@ -756,7 +806,10 @@ function App() {
|
|
| 756 |
return;
|
| 757 |
}
|
| 758 |
|
| 759 |
-
const title = `Chat - ${
|
|
|
|
|
|
|
|
|
|
| 760 |
const newChat: SavedChat = {
|
| 761 |
id: Date.now().toString(),
|
| 762 |
title,
|
|
@@ -891,12 +944,7 @@ function App() {
|
|
| 891 |
toast.success("Removed from saved items");
|
| 892 |
};
|
| 893 |
|
| 894 |
-
const handleCreateWorkspace = (payload: {
|
| 895 |
-
name: string;
|
| 896 |
-
category: "course" | "personal";
|
| 897 |
-
courseId?: string;
|
| 898 |
-
invites: string[];
|
| 899 |
-
}) => {
|
| 900 |
const id = `group-${Date.now()}`;
|
| 901 |
const avatar = `https://api.dicebear.com/7.x/shapes/svg?seed=${encodeURIComponent(payload.name)}`;
|
| 902 |
|
|
@@ -1059,8 +1107,8 @@ function App() {
|
|
| 1059 |
language={language}
|
| 1060 |
onLearningModeChange={setLearningMode}
|
| 1061 |
onLanguageChange={setLanguage}
|
| 1062 |
-
spaceType={
|
| 1063 |
-
groupMembers={
|
| 1064 |
user={user}
|
| 1065 |
onLogin={setUser}
|
| 1066 |
onLogout={() => setUser(null)}
|
|
@@ -1075,7 +1123,7 @@ function App() {
|
|
| 1075 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 1076 |
onRenameSavedChat={handleRenameSavedChat}
|
| 1077 |
currentWorkspaceId={currentWorkspaceId}
|
| 1078 |
-
workspaces={
|
| 1079 |
selectedCourse={currentCourseId}
|
| 1080 |
availableCourses={availableCourses}
|
| 1081 |
/>
|
|
@@ -1106,8 +1154,8 @@ function App() {
|
|
| 1106 |
language={language}
|
| 1107 |
onLearningModeChange={setLearningMode}
|
| 1108 |
onLanguageChange={setLanguage}
|
| 1109 |
-
spaceType={
|
| 1110 |
-
groupMembers={
|
| 1111 |
user={user}
|
| 1112 |
onLogin={setUser}
|
| 1113 |
onLogout={() => setUser(null)}
|
|
@@ -1122,7 +1170,7 @@ function App() {
|
|
| 1122 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 1123 |
onRenameSavedChat={handleRenameSavedChat}
|
| 1124 |
currentWorkspaceId={currentWorkspaceId}
|
| 1125 |
-
workspaces={
|
| 1126 |
selectedCourse={currentCourseId}
|
| 1127 |
availableCourses={availableCourses}
|
| 1128 |
/>
|
|
|
|
| 1 |
// web/src/App.tsx
|
| 2 |
import React, { useState, useEffect, useRef, useMemo } from "react";
|
| 3 |
import { Header } from "./components/Header";
|
| 4 |
+
// import { LeftSidebar } from "./components/LeftSidebar";
|
| 5 |
import { ChatArea } from "./components/ChatArea";
|
| 6 |
import { LoginScreen } from "./components/LoginScreen";
|
| 7 |
import { ProfileEditor } from "./components/ProfileEditor";
|
|
|
|
| 11 |
import { Button } from "./components/ui/button";
|
| 12 |
import { Toaster } from "./components/ui/sonner";
|
| 13 |
import { toast } from "sonner";
|
| 14 |
+
import { LeftSidebar } from "./components/sidebar/LeftSidebar";
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
// backend API bindings
|
| 17 |
import { apiChat, apiUpload, apiMemoryline } from "./lib/api";
|
|
|
|
| 391 |
|
| 392 |
const spaceType: SpaceType = currentWorkspace?.type || "individual";
|
| 393 |
|
| 394 |
+
// =========================
|
| 395 |
+
// ✅ Scheme 1: "My Space" uses Group-like sidebar view model
|
| 396 |
+
// - Only affects LeftSidebar rendering (NOT ChatArea / Header logic)
|
| 397 |
+
// =========================
|
| 398 |
+
const mySpaceCourseInfo = useMemo(() => {
|
| 399 |
+
return availableCourses.find((c) => c.id === currentCourseId);
|
| 400 |
+
}, [availableCourses, currentCourseId]);
|
| 401 |
+
|
| 402 |
+
const mySpaceUserMember: GroupMember | null = useMemo(() => {
|
| 403 |
+
if (!user) return null;
|
| 404 |
+
return {
|
| 405 |
+
id: user.email,
|
| 406 |
+
name: user.name,
|
| 407 |
+
email: user.email,
|
| 408 |
+
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(user.email)}`,
|
| 409 |
+
};
|
| 410 |
+
}, [user]);
|
| 411 |
+
|
| 412 |
+
const clareMember: GroupMember = useMemo(
|
| 413 |
+
() => ({ id: "clare", name: "Clare AI", email: "clare@ai.assistant", isAI: true }),
|
| 414 |
+
[]
|
| 415 |
+
);
|
| 416 |
+
|
| 417 |
+
// For sidebar only: treat "My Space" as a course/group-like workspace so Group UI blocks can render.
|
| 418 |
+
const sidebarWorkspaces: Workspace[] = useMemo(() => {
|
| 419 |
+
if (!workspaces?.length) return workspaces;
|
| 420 |
+
|
| 421 |
+
// If user is not ready, do not synthesize
|
| 422 |
+
if (!mySpaceUserMember) return workspaces;
|
| 423 |
+
|
| 424 |
+
return workspaces.map((w) => {
|
| 425 |
+
if (w.id !== "individual") return w;
|
| 426 |
+
|
| 427 |
+
return {
|
| 428 |
+
...w,
|
| 429 |
+
// keep type as "individual" to avoid breaking other list logic,
|
| 430 |
+
// but fill the fields that Group UI depends on
|
| 431 |
+
category: "course",
|
| 432 |
+
courseName: mySpaceCourseInfo?.name || w.courseName || "My Course",
|
| 433 |
+
courseInfo: mySpaceCourseInfo,
|
| 434 |
+
members: [clareMember, mySpaceUserMember],
|
| 435 |
+
};
|
| 436 |
+
});
|
| 437 |
+
}, [workspaces, mySpaceCourseInfo, mySpaceUserMember, clareMember]);
|
| 438 |
+
|
| 439 |
+
// Some sidebars use spaceType to gate rendering; for My Space we present it as "group" in sidebar only.
|
| 440 |
+
const sidebarSpaceType: SpaceType = useMemo(() => {
|
| 441 |
+
return currentWorkspaceId === "individual" ? "group" : spaceType;
|
| 442 |
+
}, [currentWorkspaceId, spaceType]);
|
| 443 |
+
|
| 444 |
+
// Sidebar-only "members" list
|
| 445 |
+
const sidebarGroupMembers: GroupMember[] = useMemo(() => {
|
| 446 |
+
if (currentWorkspaceId === "individual" && mySpaceUserMember) {
|
| 447 |
+
return [clareMember, mySpaceUserMember];
|
| 448 |
+
}
|
| 449 |
+
return groupMembers;
|
| 450 |
+
}, [currentWorkspaceId, mySpaceUserMember, clareMember, groupMembers]);
|
| 451 |
+
|
| 452 |
useEffect(() => {
|
| 453 |
if (!currentWorkspace) return;
|
| 454 |
|
|
|
|
| 729 |
const newFiles: UploadedFile[] = files.map((file) => ({ file, type: "other" as FileType }));
|
| 730 |
setUploadedFiles((prev) => [...prev, ...newFiles]);
|
| 731 |
};
|
|
|
|
| 732 |
|
| 733 |
+
const handleRemoveFile = (file: File) => setUploadedFiles((prev) => prev.filter((u) => u.file !== file));
|
| 734 |
|
| 735 |
// ✅ CRITICAL FIX:
|
| 736 |
// - use functional setState to avoid stale closure
|
|
|
|
| 777 |
|
| 778 |
return chat.messages.every((savedMsg, idx) => {
|
| 779 |
const currentMsg = messages[idx];
|
| 780 |
+
return savedMsg.id === currentMsg.id && savedMsg.role === currentMsg.role && savedMsg.content === currentMsg.content;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
});
|
| 782 |
}) || null
|
| 783 |
);
|
|
|
|
| 806 |
return;
|
| 807 |
}
|
| 808 |
|
| 809 |
+
const title = `Chat - ${
|
| 810 |
+
chatMode === "ask" ? "Ask" : chatMode === "review" ? "Review" : "Quiz"
|
| 811 |
+
} - ${new Date().toLocaleDateString()}`;
|
| 812 |
+
|
| 813 |
const newChat: SavedChat = {
|
| 814 |
id: Date.now().toString(),
|
| 815 |
title,
|
|
|
|
| 944 |
toast.success("Removed from saved items");
|
| 945 |
};
|
| 946 |
|
| 947 |
+
const handleCreateWorkspace = (payload: { name: string; category: "course" | "personal"; courseId?: string; invites: string[] }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 948 |
const id = `group-${Date.now()}`;
|
| 949 |
const avatar = `https://api.dicebear.com/7.x/shapes/svg?seed=${encodeURIComponent(payload.name)}`;
|
| 950 |
|
|
|
|
| 1107 |
language={language}
|
| 1108 |
onLearningModeChange={setLearningMode}
|
| 1109 |
onLanguageChange={setLanguage}
|
| 1110 |
+
spaceType={sidebarSpaceType}
|
| 1111 |
+
groupMembers={sidebarGroupMembers}
|
| 1112 |
user={user}
|
| 1113 |
onLogin={setUser}
|
| 1114 |
onLogout={() => setUser(null)}
|
|
|
|
| 1123 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 1124 |
onRenameSavedChat={handleRenameSavedChat}
|
| 1125 |
currentWorkspaceId={currentWorkspaceId}
|
| 1126 |
+
workspaces={sidebarWorkspaces}
|
| 1127 |
selectedCourse={currentCourseId}
|
| 1128 |
availableCourses={availableCourses}
|
| 1129 |
/>
|
|
|
|
| 1154 |
language={language}
|
| 1155 |
onLearningModeChange={setLearningMode}
|
| 1156 |
onLanguageChange={setLanguage}
|
| 1157 |
+
spaceType={sidebarSpaceType}
|
| 1158 |
+
groupMembers={sidebarGroupMembers}
|
| 1159 |
user={user}
|
| 1160 |
onLogin={setUser}
|
| 1161 |
onLogout={() => setUser(null)}
|
|
|
|
| 1170 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 1171 |
onRenameSavedChat={handleRenameSavedChat}
|
| 1172 |
currentWorkspaceId={currentWorkspaceId}
|
| 1173 |
+
workspaces={sidebarWorkspaces}
|
| 1174 |
selectedCourse={currentCourseId}
|
| 1175 |
availableCourses={availableCourses}
|
| 1176 |
/>
|