AI_Agent_Final / web /src /components /sidebar /SavedChatSection.tsx
SarahXia0405's picture
Update web/src/components/sidebar/SavedChatSection.tsx
6a18bec verified
// web/src/components/sidebar/SavedChatSection.tsx
import React, { useEffect, useState } from "react";
import { Card } from "../ui/card";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
import { Bookmark, Trash2, Pencil } from "lucide-react";
import type { SavedChat } from "../../App";
export function SavedChatSection({
isLoggedIn,
savedChats,
onLoadChat,
onDeleteSavedChat,
onRenameSavedChat,
}: {
isLoggedIn: boolean;
savedChats: SavedChat[];
onLoadChat: (chat: SavedChat) => void;
onDeleteSavedChat: (id: string) => void;
onRenameSavedChat?: (id: string, newTitle: string) => void;
}) {
const [editingId, setEditingId] = useState<string | null>(null);
const [draftTitle, setDraftTitle] = useState<string>("");
// Keep draft in sync if list changes while editing (e.g., load, delete)
useEffect(() => {
if (!editingId) return;
const current = savedChats.find((c) => c.id === editingId);
if (!current) {
setEditingId(null);
setDraftTitle("");
return;
}
// Only update draft if it was empty (avoid overwriting user's typing)
if (!draftTitle) setDraftTitle(current.title || "");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [savedChats, editingId]);
if (!isLoggedIn) return null;
const beginRename = (chat: SavedChat) => {
if (!onRenameSavedChat) return;
setEditingId(chat.id);
setDraftTitle(chat.title || "");
};
const cancelRename = () => {
setEditingId(null);
setDraftTitle("");
};
const saveRename = () => {
if (!editingId || !onRenameSavedChat) return;
const next = draftTitle.trim();
const current = savedChats.find((c) => c.id === editingId);
const fallback = current?.title || "";
// If user clears the title, revert to previous (keeps it clean)
const finalTitle = next || fallback;
if (finalTitle && finalTitle !== current?.title) {
onRenameSavedChat(editingId, finalTitle);
}
setEditingId(null);
setDraftTitle("");
};
return (
// ✅ NO scrolling here. Scrolling is owned by LeftSidebar's scroll container.
<div className="w-full">
{/* header */}
<div className="px-4 py-3">
<div className="flex items-center gap-2">
<Bookmark className="h-4 w-4" />
<div className="font-semibold">Saved Chat</div>
</div>
</div>
<Separator />
{/* content (normal flow; NO overflow-y-auto) */}
<div className="px-4 py-4">
{savedChats.length === 0 ? (
<Card className="p-8 text-center">
<div className="text-sm text-muted-foreground">No saved chats yet</div>
<div className="text-xs text-muted-foreground mt-1">
Save conversations to view them here
</div>
</Card>
) : (
<div className="space-y-2">
{savedChats.map((chat) => {
const isEditing = editingId === chat.id;
const renameEnabled = !!onRenameSavedChat;
return (
<Card
key={chat.id}
className="p-3 group"
>
<div className="flex items-start justify-between gap-2">
{/* Left: title + timestamp (click loads, but disabled while editing) */}
<div className="text-left flex-1 min-w-0">
{!isEditing ? (
<button
type="button"
onClick={() => onLoadChat(chat)}
className="text-left w-full"
>
<div className="text-sm font-medium leading-5 truncate">
{chat.title}
</div>
<div className="text-xs text-muted-foreground mt-1">
{chat.timestamp.toLocaleString()}
</div>
</button>
) : (
<div className="w-full">
<input
autoFocus
value={draftTitle}
onChange={(e) => setDraftTitle(e.target.value)}
onBlur={saveRename}
onKeyDown={(e) => {
if (e.key === "Enter") saveRename();
if (e.key === "Escape") cancelRename();
}}
className="w-full h-8 px-2 rounded-md border bg-background text-sm outline-none focus:ring-2 focus:ring-ring"
/>
<div className="text-[11px] text-muted-foreground mt-1">
Enter to save · Esc to cancel
</div>
</div>
)}
</div>
{/* Right: actions (hover show) */}
<div className="flex items-center gap-1">
{/* ✏️ Rename */}
{renameEnabled && !isEditing && (
<Button
variant="ghost"
size="icon"
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => beginRename(chat)}
title="Rename"
>
<Pencil className="h-4 w-4" />
</Button>
)}
{/* 🗑 Delete */}
<Button
variant="ghost"
size="icon"
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => onDeleteSavedChat(chat.id)}
title="Delete"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</Card>
);
})}
</div>
)}
</div>
</div>
);
}