Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +63 -12
web/src/App.tsx
CHANGED
|
@@ -120,6 +120,33 @@ function mapLanguagePref(lang: Language): string {
|
|
| 120 |
return "Auto";
|
| 121 |
}
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
function App() {
|
| 124 |
const [isDarkMode, setIsDarkMode] = useState(() => {
|
| 125 |
const saved = localStorage.getItem("theme");
|
|
@@ -281,6 +308,32 @@ function App() {
|
|
| 281 |
|
| 282 |
const [savedChats, setSavedChats] = useState<SavedChat[]>([]);
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
const [groupMembers] = useState<GroupMember[]>([
|
| 285 |
{ id: "clare", name: "Clare AI", email: "clare@ai.assistant", isAI: true },
|
| 286 |
{ id: "1", name: "Sarah Johnson", email: "sarah.j@university.edu" },
|
|
@@ -388,7 +441,6 @@ function App() {
|
|
| 388 |
|
| 389 |
// =========================
|
| 390 |
// ✅ Review Star (按天) state
|
| 391 |
-
// scope:默认按 workspace 统计(你也可以改成 currentCourseId)
|
| 392 |
// =========================
|
| 393 |
const reviewStarKey = useMemo(() => {
|
| 394 |
if (!user) return "";
|
|
@@ -397,7 +449,6 @@ function App() {
|
|
| 397 |
|
| 398 |
const [reviewStarState, setReviewStarState] = useState<ReviewStarState | null>(null);
|
| 399 |
|
| 400 |
-
// 进入 Review tab 或 workspace 切换时:normalize(跨天归零 todayCount -> 星星自动变暗)
|
| 401 |
useEffect(() => {
|
| 402 |
if (!user || !reviewStarKey) return;
|
| 403 |
if (chatMode !== "review") return;
|
|
@@ -667,6 +718,16 @@ function App() {
|
|
| 667 |
);
|
| 668 |
};
|
| 669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
const handleSaveChat = () => {
|
| 671 |
if (messages.length <= 1) {
|
| 672 |
toast.info("No conversation to save");
|
|
@@ -707,16 +768,6 @@ function App() {
|
|
| 707 |
toast.success("Chat loaded!");
|
| 708 |
};
|
| 709 |
|
| 710 |
-
const handleDeleteSavedChat = (id: string) => {
|
| 711 |
-
setSavedChats((prev) => prev.filter((chat) => chat.id !== id));
|
| 712 |
-
toast.success("Chat deleted");
|
| 713 |
-
};
|
| 714 |
-
|
| 715 |
-
const handleRenameSavedChat = (id: string, newTitle: string) => {
|
| 716 |
-
setSavedChats((prev) => prev.map((chat) => (chat.id === id ? { ...chat, title: newTitle } : chat)));
|
| 717 |
-
toast.success("Chat renamed");
|
| 718 |
-
};
|
| 719 |
-
|
| 720 |
const handleClearConversation = (shouldSave: boolean = false) => {
|
| 721 |
if (shouldSave) handleSaveChat();
|
| 722 |
|
|
|
|
| 120 |
return "Auto";
|
| 121 |
}
|
| 122 |
|
| 123 |
+
// ✅ NEW: localStorage helpers for saved chats
|
| 124 |
+
function savedChatsStorageKey(email: string) {
|
| 125 |
+
return `saved_chats::${email}`;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
function hydrateSavedChats(raw: any): SavedChat[] {
|
| 129 |
+
if (!Array.isArray(raw)) return [];
|
| 130 |
+
return raw
|
| 131 |
+
.map((c: any) => {
|
| 132 |
+
try {
|
| 133 |
+
return {
|
| 134 |
+
...c,
|
| 135 |
+
timestamp: c?.timestamp ? new Date(c.timestamp) : new Date(),
|
| 136 |
+
messages: Array.isArray(c?.messages)
|
| 137 |
+
? c.messages.map((m: any) => ({
|
| 138 |
+
...m,
|
| 139 |
+
timestamp: m?.timestamp ? new Date(m.timestamp) : new Date(),
|
| 140 |
+
}))
|
| 141 |
+
: [],
|
| 142 |
+
} as SavedChat;
|
| 143 |
+
} catch {
|
| 144 |
+
return null;
|
| 145 |
+
}
|
| 146 |
+
})
|
| 147 |
+
.filter(Boolean) as SavedChat[];
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
function App() {
|
| 151 |
const [isDarkMode, setIsDarkMode] = useState(() => {
|
| 152 |
const saved = localStorage.getItem("theme");
|
|
|
|
| 308 |
|
| 309 |
const [savedChats, setSavedChats] = useState<SavedChat[]>([]);
|
| 310 |
|
| 311 |
+
// ✅ NEW: load saved chats after login
|
| 312 |
+
useEffect(() => {
|
| 313 |
+
if (!user?.email) return;
|
| 314 |
+
try {
|
| 315 |
+
const raw = localStorage.getItem(savedChatsStorageKey(user.email));
|
| 316 |
+
if (!raw) {
|
| 317 |
+
setSavedChats([]);
|
| 318 |
+
return;
|
| 319 |
+
}
|
| 320 |
+
const parsed = JSON.parse(raw);
|
| 321 |
+
setSavedChats(hydrateSavedChats(parsed));
|
| 322 |
+
} catch {
|
| 323 |
+
setSavedChats([]);
|
| 324 |
+
}
|
| 325 |
+
}, [user?.email]);
|
| 326 |
+
|
| 327 |
+
// ✅ NEW: persist saved chats whenever changed
|
| 328 |
+
useEffect(() => {
|
| 329 |
+
if (!user?.email) return;
|
| 330 |
+
try {
|
| 331 |
+
localStorage.setItem(savedChatsStorageKey(user.email), JSON.stringify(savedChats));
|
| 332 |
+
} catch {
|
| 333 |
+
// ignore
|
| 334 |
+
}
|
| 335 |
+
}, [savedChats, user?.email]);
|
| 336 |
+
|
| 337 |
const [groupMembers] = useState<GroupMember[]>([
|
| 338 |
{ id: "clare", name: "Clare AI", email: "clare@ai.assistant", isAI: true },
|
| 339 |
{ id: "1", name: "Sarah Johnson", email: "sarah.j@university.edu" },
|
|
|
|
| 441 |
|
| 442 |
// =========================
|
| 443 |
// ✅ Review Star (按天) state
|
|
|
|
| 444 |
// =========================
|
| 445 |
const reviewStarKey = useMemo(() => {
|
| 446 |
if (!user) return "";
|
|
|
|
| 449 |
|
| 450 |
const [reviewStarState, setReviewStarState] = useState<ReviewStarState | null>(null);
|
| 451 |
|
|
|
|
| 452 |
useEffect(() => {
|
| 453 |
if (!user || !reviewStarKey) return;
|
| 454 |
if (chatMode !== "review") return;
|
|
|
|
| 718 |
);
|
| 719 |
};
|
| 720 |
|
| 721 |
+
const handleDeleteSavedChat = (id: string) => {
|
| 722 |
+
setSavedChats((prev) => prev.filter((chat) => chat.id !== id));
|
| 723 |
+
toast.success("Chat deleted");
|
| 724 |
+
};
|
| 725 |
+
|
| 726 |
+
const handleRenameSavedChat = (id: string, newTitle: string) => {
|
| 727 |
+
setSavedChats((prev) => prev.map((chat) => (chat.id === id ? { ...chat, title: newTitle } : chat)));
|
| 728 |
+
toast.success("Chat renamed");
|
| 729 |
+
};
|
| 730 |
+
|
| 731 |
const handleSaveChat = () => {
|
| 732 |
if (messages.length <= 1) {
|
| 733 |
toast.info("No conversation to save");
|
|
|
|
| 768 |
toast.success("Chat loaded!");
|
| 769 |
};
|
| 770 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
const handleClearConversation = (shouldSave: boolean = false) => {
|
| 772 |
if (shouldSave) handleSaveChat();
|
| 773 |
|