Spaces:
Sleeping
Sleeping
Update web/src/components/LeftSidebar.tsx
Browse files
web/src/components/LeftSidebar.tsx
CHANGED
|
@@ -15,7 +15,9 @@ import { jsPDF } from "jspdf";
|
|
| 15 |
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog";
|
| 16 |
import type { CourseInfo } from "../App";
|
| 17 |
|
| 18 |
-
//
|
|
|
|
|
|
|
| 19 |
function SavedChatItem({
|
| 20 |
chat,
|
| 21 |
onLoadChat,
|
|
@@ -69,10 +71,7 @@ function SavedChatItem({
|
|
| 69 |
|
| 70 |
const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
| 71 |
const relatedTarget = e.relatedTarget as HTMLElement;
|
| 72 |
-
if (
|
| 73 |
-
relatedTarget &&
|
| 74 |
-
(cancelButtonRef.current?.contains(relatedTarget) || saveButtonRef.current?.contains(relatedTarget))
|
| 75 |
-
) {
|
| 76 |
return;
|
| 77 |
}
|
| 78 |
if (editTitle.trim() && editTitle !== originalTitle && onRenameSavedChat) {
|
|
@@ -166,7 +165,13 @@ function SavedChatItem({
|
|
| 166 |
) : (
|
| 167 |
<>
|
| 168 |
{onRenameSavedChat && (
|
| 169 |
-
<Button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
<Edit2 className="h-3 w-3" />
|
| 171 |
</Button>
|
| 172 |
)}
|
|
@@ -270,6 +275,29 @@ export function LeftSidebar({
|
|
| 270 |
const [isDownloading, setIsDownloading] = useState(false);
|
| 271 |
const [copied, setCopied] = useState(false);
|
| 272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
const handleLogin = () => {
|
| 274 |
if (!name.trim() || !email.trim()) {
|
| 275 |
toast.error("Please fill in all fields");
|
|
@@ -424,8 +452,7 @@ export function LeftSidebar({
|
|
| 424 |
}
|
| 425 |
};
|
| 426 |
|
| 427 |
-
const filteredSavedItems = savedItems
|
| 428 |
-
.filter((item) => item.workspaceId === currentWorkspaceId);
|
| 429 |
|
| 430 |
const defaultCourses = [
|
| 431 |
{ id: "course1", name: "Introduction to AI" },
|
|
@@ -494,6 +521,7 @@ export function LeftSidebar({
|
|
| 494 |
const courseDisplayInfo = getCourseDisplayInfo();
|
| 495 |
|
| 496 |
return (
|
|
|
|
| 497 |
<div className="h-full min-h-0 flex flex-col overflow-hidden">
|
| 498 |
{/* Top fixed blocks */}
|
| 499 |
{isLoggedIn && courseDisplayInfo && (
|
|
@@ -610,14 +638,18 @@ export function LeftSidebar({
|
|
| 610 |
</div>
|
| 611 |
)}
|
| 612 |
|
| 613 |
-
{/* Saved Chat: ONLY this list scrolls */}
|
| 614 |
{isLoggedIn && (
|
| 615 |
-
<div className="flex-1 min-h-0 flex flex-col">
|
| 616 |
<div className="p-4 border-b border-border flex-shrink-0">
|
| 617 |
<h3 className="text-base font-medium">Saved Chat</h3>
|
| 618 |
</div>
|
| 619 |
|
| 620 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
{savedChats.length === 0 ? (
|
| 622 |
<div className="text-sm text-muted-foreground text-center py-4">
|
| 623 |
<MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
@@ -641,7 +673,7 @@ export function LeftSidebar({
|
|
| 641 |
</div>
|
| 642 |
)}
|
| 643 |
|
| 644 |
-
{/* Saved Item Dialog (
|
| 645 |
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
| 646 |
<DialogContent className="max-w-4xl max-h-[85vh] flex flex-col">
|
| 647 |
<DialogHeader>
|
|
|
|
| 15 |
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog";
|
| 16 |
import type { CourseInfo } from "../App";
|
| 17 |
|
| 18 |
+
// ================================
|
| 19 |
+
// Saved Chat Item (unchanged)
|
| 20 |
+
// ================================
|
| 21 |
function SavedChatItem({
|
| 22 |
chat,
|
| 23 |
onLoadChat,
|
|
|
|
| 71 |
|
| 72 |
const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
| 73 |
const relatedTarget = e.relatedTarget as HTMLElement;
|
| 74 |
+
if (relatedTarget && (cancelButtonRef.current?.contains(relatedTarget) || saveButtonRef.current?.contains(relatedTarget))) {
|
|
|
|
|
|
|
|
|
|
| 75 |
return;
|
| 76 |
}
|
| 77 |
if (editTitle.trim() && editTitle !== originalTitle && onRenameSavedChat) {
|
|
|
|
| 165 |
) : (
|
| 166 |
<>
|
| 167 |
{onRenameSavedChat && (
|
| 168 |
+
<Button
|
| 169 |
+
variant="ghost"
|
| 170 |
+
size="icon"
|
| 171 |
+
className="h-5 w-5 flex-shrink-0 hover:bg-muted"
|
| 172 |
+
onClick={handleStartEdit}
|
| 173 |
+
title="Rename chat"
|
| 174 |
+
>
|
| 175 |
<Edit2 className="h-3 w-3" />
|
| 176 |
</Button>
|
| 177 |
)}
|
|
|
|
| 275 |
const [isDownloading, setIsDownloading] = useState(false);
|
| 276 |
const [copied, setCopied] = useState(false);
|
| 277 |
|
| 278 |
+
// ✅ NEW: ref for Saved scroll area
|
| 279 |
+
const savedScrollRef = useRef<HTMLDivElement>(null);
|
| 280 |
+
|
| 281 |
+
// ✅ NEW: hard-stop scroll chaining from Saved area to outer containers
|
| 282 |
+
useEffect(() => {
|
| 283 |
+
const el = savedScrollRef.current;
|
| 284 |
+
if (!el) return;
|
| 285 |
+
|
| 286 |
+
const onWheel = (e: WheelEvent) => {
|
| 287 |
+
e.stopPropagation();
|
| 288 |
+
|
| 289 |
+
const atTop = el.scrollTop <= 0;
|
| 290 |
+
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
| 291 |
+
|
| 292 |
+
if ((atTop && e.deltaY < 0) || (atBottom && e.deltaY > 0)) {
|
| 293 |
+
e.preventDefault();
|
| 294 |
+
}
|
| 295 |
+
};
|
| 296 |
+
|
| 297 |
+
el.addEventListener("wheel", onWheel, { passive: false });
|
| 298 |
+
return () => el.removeEventListener("wheel", onWheel);
|
| 299 |
+
}, []);
|
| 300 |
+
|
| 301 |
const handleLogin = () => {
|
| 302 |
if (!name.trim() || !email.trim()) {
|
| 303 |
toast.error("Please fill in all fields");
|
|
|
|
| 452 |
}
|
| 453 |
};
|
| 454 |
|
| 455 |
+
const filteredSavedItems = savedItems.filter((item) => item.workspaceId === currentWorkspaceId);
|
|
|
|
| 456 |
|
| 457 |
const defaultCourses = [
|
| 458 |
{ id: "course1", name: "Introduction to AI" },
|
|
|
|
| 521 |
const courseDisplayInfo = getCourseDisplayInfo();
|
| 522 |
|
| 523 |
return (
|
| 524 |
+
// ✅ LeftSidebar itself never scrolls. Only the Saved list scrolls.
|
| 525 |
<div className="h-full min-h-0 flex flex-col overflow-hidden">
|
| 526 |
{/* Top fixed blocks */}
|
| 527 |
{isLoggedIn && courseDisplayInfo && (
|
|
|
|
| 638 |
</div>
|
| 639 |
)}
|
| 640 |
|
| 641 |
+
{/* ✅ Saved Chat: ONLY this list scrolls */}
|
| 642 |
{isLoggedIn && (
|
| 643 |
+
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
| 644 |
<div className="p-4 border-b border-border flex-shrink-0">
|
| 645 |
<h3 className="text-base font-medium">Saved Chat</h3>
|
| 646 |
</div>
|
| 647 |
|
| 648 |
+
<div
|
| 649 |
+
ref={savedScrollRef}
|
| 650 |
+
className="flex-1 min-h-0 overflow-y-auto p-4"
|
| 651 |
+
style={{ overscrollBehavior: "contain" }}
|
| 652 |
+
>
|
| 653 |
{savedChats.length === 0 ? (
|
| 654 |
<div className="text-sm text-muted-foreground text-center py-4">
|
| 655 |
<MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
|
|
| 673 |
</div>
|
| 674 |
)}
|
| 675 |
|
| 676 |
+
{/* Saved Item Dialog (keep your logic) */}
|
| 677 |
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
| 678 |
<DialogContent className="max-w-4xl max-h-[85vh] flex flex-col">
|
| 679 |
<DialogHeader>
|