SarahXia0405 commited on
Commit
6a18bec
·
verified ·
1 Parent(s): 8cf6e59

Update web/src/components/sidebar/SavedChatSection.tsx

Browse files
web/src/components/sidebar/SavedChatSection.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import React from "react";
 
2
  import { Card } from "../ui/card";
3
  import { Button } from "../ui/button";
4
  import { Separator } from "../ui/separator";
5
- import { Bookmark, Trash2 } from "lucide-react";
6
  import type { SavedChat } from "../../App";
7
 
8
  export function SavedChatSection({
@@ -18,12 +19,58 @@ export function SavedChatSection({
18
  onDeleteSavedChat: (id: string) => void;
19
  onRenameSavedChat?: (id: string, newTitle: string) => void;
20
  }) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  if (!isLoggedIn) return null;
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return (
24
  // ✅ NO scrolling here. Scrolling is owned by LeftSidebar's scroll container.
25
  <div className="w-full">
26
- {/* header (stays at top of scroll content; LeftSidebar decides if it should be sticky/fixed) */}
27
  <div className="px-4 py-3">
28
  <div className="flex items-center gap-2">
29
  <Bookmark className="h-4 w-4" />
@@ -44,32 +91,81 @@ export function SavedChatSection({
44
  </Card>
45
  ) : (
46
  <div className="space-y-2">
47
- {savedChats.map((chat) => (
48
- <Card key={chat.id} className="p-3">
49
- <div className="flex items-start justify-between gap-2">
50
- <button
51
- type="button"
52
- onClick={() => onLoadChat(chat)}
53
- className="text-left flex-1"
54
- >
55
- <div className="text-sm font-medium leading-5">{chat.title}</div>
56
- <div className="text-xs text-muted-foreground mt-1">
57
- {chat.timestamp.toLocaleString()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </div>
59
- </button>
60
-
61
- <Button
62
- variant="ghost"
63
- size="icon"
64
- className="h-8 w-8"
65
- onClick={() => onDeleteSavedChat(chat.id)}
66
- title="Delete"
67
- >
68
- <Trash2 className="h-4 w-4" />
69
- </Button>
70
- </div>
71
- </Card>
72
- ))}
73
  </div>
74
  )}
75
  </div>
 
1
+ // web/src/components/sidebar/SavedChatSection.tsx
2
+ import React, { useEffect, useState } from "react";
3
  import { Card } from "../ui/card";
4
  import { Button } from "../ui/button";
5
  import { Separator } from "../ui/separator";
6
+ import { Bookmark, Trash2, Pencil } from "lucide-react";
7
  import type { SavedChat } from "../../App";
8
 
9
  export function SavedChatSection({
 
19
  onDeleteSavedChat: (id: string) => void;
20
  onRenameSavedChat?: (id: string, newTitle: string) => void;
21
  }) {
22
+ const [editingId, setEditingId] = useState<string | null>(null);
23
+ const [draftTitle, setDraftTitle] = useState<string>("");
24
+
25
+ // Keep draft in sync if list changes while editing (e.g., load, delete)
26
+ useEffect(() => {
27
+ if (!editingId) return;
28
+ const current = savedChats.find((c) => c.id === editingId);
29
+ if (!current) {
30
+ setEditingId(null);
31
+ setDraftTitle("");
32
+ return;
33
+ }
34
+ // Only update draft if it was empty (avoid overwriting user's typing)
35
+ if (!draftTitle) setDraftTitle(current.title || "");
36
+ // eslint-disable-next-line react-hooks/exhaustive-deps
37
+ }, [savedChats, editingId]);
38
+
39
  if (!isLoggedIn) return null;
40
 
41
+ const beginRename = (chat: SavedChat) => {
42
+ if (!onRenameSavedChat) return;
43
+ setEditingId(chat.id);
44
+ setDraftTitle(chat.title || "");
45
+ };
46
+
47
+ const cancelRename = () => {
48
+ setEditingId(null);
49
+ setDraftTitle("");
50
+ };
51
+
52
+ const saveRename = () => {
53
+ if (!editingId || !onRenameSavedChat) return;
54
+
55
+ const next = draftTitle.trim();
56
+ const current = savedChats.find((c) => c.id === editingId);
57
+ const fallback = current?.title || "";
58
+
59
+ // If user clears the title, revert to previous (keeps it clean)
60
+ const finalTitle = next || fallback;
61
+
62
+ if (finalTitle && finalTitle !== current?.title) {
63
+ onRenameSavedChat(editingId, finalTitle);
64
+ }
65
+
66
+ setEditingId(null);
67
+ setDraftTitle("");
68
+ };
69
+
70
  return (
71
  // ✅ NO scrolling here. Scrolling is owned by LeftSidebar's scroll container.
72
  <div className="w-full">
73
+ {/* header */}
74
  <div className="px-4 py-3">
75
  <div className="flex items-center gap-2">
76
  <Bookmark className="h-4 w-4" />
 
91
  </Card>
92
  ) : (
93
  <div className="space-y-2">
94
+ {savedChats.map((chat) => {
95
+ const isEditing = editingId === chat.id;
96
+ const renameEnabled = !!onRenameSavedChat;
97
+
98
+ return (
99
+ <Card
100
+ key={chat.id}
101
+ className="p-3 group"
102
+ >
103
+ <div className="flex items-start justify-between gap-2">
104
+ {/* Left: title + timestamp (click loads, but disabled while editing) */}
105
+ <div className="text-left flex-1 min-w-0">
106
+ {!isEditing ? (
107
+ <button
108
+ type="button"
109
+ onClick={() => onLoadChat(chat)}
110
+ className="text-left w-full"
111
+ >
112
+ <div className="text-sm font-medium leading-5 truncate">
113
+ {chat.title}
114
+ </div>
115
+ <div className="text-xs text-muted-foreground mt-1">
116
+ {chat.timestamp.toLocaleString()}
117
+ </div>
118
+ </button>
119
+ ) : (
120
+ <div className="w-full">
121
+ <input
122
+ autoFocus
123
+ value={draftTitle}
124
+ onChange={(e) => setDraftTitle(e.target.value)}
125
+ onBlur={saveRename}
126
+ onKeyDown={(e) => {
127
+ if (e.key === "Enter") saveRename();
128
+ if (e.key === "Escape") cancelRename();
129
+ }}
130
+ className="w-full h-8 px-2 rounded-md border bg-background text-sm outline-none focus:ring-2 focus:ring-ring"
131
+ />
132
+ <div className="text-[11px] text-muted-foreground mt-1">
133
+ Enter to save · Esc to cancel
134
+ </div>
135
+ </div>
136
+ )}
137
+ </div>
138
+
139
+ {/* Right: actions (hover show) */}
140
+ <div className="flex items-center gap-1">
141
+ {/* ✏️ Rename */}
142
+ {renameEnabled && !isEditing && (
143
+ <Button
144
+ variant="ghost"
145
+ size="icon"
146
+ className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
147
+ onClick={() => beginRename(chat)}
148
+ title="Rename"
149
+ >
150
+ <Pencil className="h-4 w-4" />
151
+ </Button>
152
+ )}
153
+
154
+ {/* 🗑 Delete */}
155
+ <Button
156
+ variant="ghost"
157
+ size="icon"
158
+ className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
159
+ onClick={() => onDeleteSavedChat(chat.id)}
160
+ title="Delete"
161
+ >
162
+ <Trash2 className="h-4 w-4" />
163
+ </Button>
164
  </div>
165
+ </div>
166
+ </Card>
167
+ );
168
+ })}
 
 
 
 
 
 
 
 
 
 
169
  </div>
170
  )}
171
  </div>