@autobe/ui 0.30.2 → 0.30.4-dev.20260324

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +261 -0
  3. package/lib/components/AutoBeChatMain.d.ts +6 -0
  4. package/lib/components/AutoBeChatMain.js +46 -37
  5. package/lib/components/AutoBeChatMain.js.map +1 -1
  6. package/lib/components/AutoBeConfigModal.js +9 -9
  7. package/lib/components/events/AutoBeEventMovie.js +0 -4
  8. package/lib/components/events/AutoBeEventMovie.js.map +1 -1
  9. package/lib/components/events/AutoBeProgressEventMovie.js +0 -10
  10. package/lib/components/events/AutoBeProgressEventMovie.js.map +1 -1
  11. package/lib/components/upload/AutoBeChatUploadBox.d.ts +1 -0
  12. package/lib/components/upload/AutoBeChatUploadBox.js +11 -16
  13. package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
  14. package/lib/context/AutoBeAgentContext.js +78 -30
  15. package/lib/context/AutoBeAgentContext.js.map +1 -1
  16. package/lib/context/SearchParamsContext.js +1 -1
  17. package/lib/context/SearchParamsContext.js.map +1 -1
  18. package/lib/structure/AutoBeListener.d.ts +1 -0
  19. package/lib/structure/AutoBeListener.js +9 -11
  20. package/lib/structure/AutoBeListener.js.map +1 -1
  21. package/package.json +2 -2
  22. package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
  23. package/src/components/AutoBeChatMain.tsx +394 -376
  24. package/src/components/AutoBeChatSidebar.tsx +414 -414
  25. package/src/components/AutoBeConfigButton.tsx +83 -83
  26. package/src/components/AutoBeConfigModal.tsx +443 -443
  27. package/src/components/AutoBeStatusButton.tsx +75 -75
  28. package/src/components/AutoBeStatusModal.tsx +486 -486
  29. package/src/components/AutoBeUserMessageMovie.tsx +27 -27
  30. package/src/components/common/ActionButton.tsx +205 -205
  31. package/src/components/common/ActionButtonGroup.tsx +80 -80
  32. package/src/components/common/AutoBeConfigInput.tsx +185 -185
  33. package/src/components/common/ChatBubble.tsx +119 -119
  34. package/src/components/common/Collapsible.tsx +95 -95
  35. package/src/components/common/CompactSessionIndicator.tsx +73 -73
  36. package/src/components/common/CompactSessionList.tsx +82 -82
  37. package/src/components/common/openai/OpenAIContent.tsx +53 -53
  38. package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
  39. package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
  40. package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
  41. package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
  42. package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
  43. package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -354
  44. package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
  45. package/src/components/events/AutoBeEventMovie.tsx +154 -158
  46. package/src/components/events/AutoBeProgressEventMovie.tsx +207 -217
  47. package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -135
  48. package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
  49. package/src/components/events/AutoBeValidateEventMovie.tsx +249 -249
  50. package/src/components/events/README.md +300 -300
  51. package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
  52. package/src/components/events/common/EventCard.tsx +61 -61
  53. package/src/components/events/common/EventContent.tsx +31 -31
  54. package/src/components/events/common/EventHeader.tsx +85 -85
  55. package/src/components/events/common/EventIcon.tsx +82 -82
  56. package/src/components/events/common/ProgressBar.tsx +64 -64
  57. package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
  58. package/src/components/events/groups/ValidateEventGroup.tsx +143 -143
  59. package/src/components/events/utils/eventGrouper.tsx +116 -116
  60. package/src/components/upload/AutoBeChatUploadBox.tsx +433 -425
  61. package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
  62. package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
  63. package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
  64. package/src/context/AutoBeAgentContext.tsx +301 -245
  65. package/src/context/AutoBeAgentSessionList.tsx +58 -58
  66. package/src/context/SearchParamsContext.tsx +49 -49
  67. package/src/icons/Receipt.tsx +74 -74
  68. package/src/structure/AutoBeListener.ts +368 -373
  69. package/tsconfig.json +9 -9
@@ -1,414 +1,414 @@
1
- import { useCallback, useEffect, useState } from "react";
2
-
3
- import { useAutoBeAgentSessionList } from "../context/AutoBeAgentSessionList";
4
- import { useSearchParams } from "../context/SearchParamsContext";
5
- import {
6
- IAutoBeAgentSession,
7
- IAutoBeAgentSessionStorageStrategy,
8
- } from "../structure";
9
- import { ActionButtonGroup } from "./common/ActionButtonGroup";
10
- import { CompactSessionList } from "./common/CompactSessionList";
11
-
12
- /** Props interface for AutoBeChatSidebar component */
13
- export interface IAutoBeChatSidebarProps {
14
- storageStrategy: IAutoBeAgentSessionStorageStrategy;
15
- /** Whether the sidebar is collapsed (true) or expanded (false) */
16
- isCollapsed: boolean;
17
- /** Function to toggle sidebar collapsed/expanded */
18
- onToggle: () => void;
19
- /** Custom className */
20
- className?: string;
21
- /** Function to select a session */
22
- onSessionSelect?: (id: string) => Promise<void> | void;
23
- /** Function to delete a session */
24
- onDeleteSession?: (id: string) => Promise<void> | void;
25
- }
26
-
27
- const collapsedWidth = "60px";
28
- const expandedWidth = "320px";
29
- /** Beautiful and modern chat sidebar component as part of layout */
30
- export const AutoBeChatSidebar = (props: IAutoBeChatSidebarProps) => {
31
- const { sessionList, refreshSessionList } = useAutoBeAgentSessionList();
32
- const { searchParams, setSearchParams } = useSearchParams();
33
- const activeSessionId = searchParams.get("session-id") ?? null;
34
- const [currentSessionId, setCurrentSessionId] = useState(
35
- Array.isArray(activeSessionId) ? activeSessionId.at(0) : activeSessionId,
36
- );
37
-
38
- const handleOnSessionSelect = useCallback(
39
- (sessionId: string) => {
40
- props.onSessionSelect?.(sessionId);
41
- setCurrentSessionId(sessionId);
42
- },
43
- [props.onSessionSelect, setSearchParams],
44
- );
45
-
46
- useEffect(() => {
47
- setCurrentSessionId(activeSessionId);
48
- }, [searchParams]);
49
-
50
- return (
51
- <div
52
- className={props.className}
53
- style={{
54
- position: "relative",
55
- height: "100%",
56
- width: props.isCollapsed ? collapsedWidth : expandedWidth,
57
- backgroundColor: "#ffffff",
58
- borderRight: "1px solid #e5e7eb",
59
- boxShadow: "2px 0 4px rgba(0, 0, 0, 0.05)",
60
- transition: "width 0.3s ease",
61
- display: "flex",
62
- flexDirection: "column",
63
- overflow: "hidden",
64
- flexShrink: 0,
65
- }}
66
- >
67
- {/* Header section */}
68
- <div
69
- style={{
70
- padding: props.isCollapsed ? "1rem 0.75rem" : "1.5rem 1.25rem 1rem",
71
- borderBottom: "1px solid #f3f4f6",
72
- backgroundColor: "#fafafa",
73
- transition: "padding 0.3s ease",
74
- }}
75
- >
76
- {/* Toggle button and title */}
77
- <div
78
- style={{
79
- display: "flex",
80
- justifyContent: props.isCollapsed ? "center" : "space-between",
81
- alignItems: "center",
82
- marginBottom: props.isCollapsed ? "0" : "1rem",
83
- transition: "all 0.3s ease",
84
- }}
85
- >
86
- {!props.isCollapsed && (
87
- <h2
88
- style={{
89
- fontSize: "1.25rem",
90
- fontWeight: "600",
91
- color: "#1f2937",
92
- margin: 0,
93
- opacity: props.isCollapsed ? 0 : 1,
94
- transition: "opacity 0.3s ease",
95
- }}
96
- >
97
- Chat History
98
- </h2>
99
- )}
100
- <button
101
- onClick={props.onToggle}
102
- style={{
103
- background: "none",
104
- border: "none",
105
- cursor: "pointer",
106
- padding: "0.5rem",
107
- borderRadius: "0.5rem",
108
- display: "flex",
109
- alignItems: "center",
110
- justifyContent: "center",
111
- transition: "background-color 0.2s ease",
112
- }}
113
- onMouseEnter={(e) => {
114
- e.currentTarget.style.backgroundColor = "#f3f4f6";
115
- }}
116
- onMouseLeave={(e) => {
117
- e.currentTarget.style.backgroundColor = "transparent";
118
- }}
119
- title={props.isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
120
- >
121
- <svg
122
- width="20"
123
- height="20"
124
- viewBox="0 0 24 24"
125
- fill="none"
126
- stroke="currentColor"
127
- strokeWidth="2"
128
- strokeLinecap="round"
129
- strokeLinejoin="round"
130
- style={{
131
- transform: props.isCollapsed
132
- ? "rotate(0deg)"
133
- : "rotate(180deg)",
134
- transition: "transform 0.3s ease",
135
- }}
136
- >
137
- <path d="M15 18l-6-6 6-6" />
138
- </svg>
139
- </button>
140
- </div>
141
- </div>
142
-
143
- {/* Conversations list */}
144
- <div
145
- style={{
146
- flex: 1,
147
- overflowY: props.isCollapsed ? "visible" : "auto",
148
- padding: props.isCollapsed ? "0.25rem" : "0.5rem",
149
- transition: "padding 0.3s ease",
150
- }}
151
- >
152
- {props.isCollapsed ? (
153
- // Collapsed state - show compact conversation indicators
154
- <CompactSessionList
155
- sessions={sessionList}
156
- activeSessionId={currentSessionId}
157
- maxItems={8}
158
- onSessionSelect={handleOnSessionSelect}
159
- />
160
- ) : (
161
- // Expanded state - show full conversation list
162
- <>
163
- {sessionList.length === 0 ? (
164
- <div
165
- style={{
166
- padding: "2rem 1rem",
167
- textAlign: "center",
168
- color: "#6b7280",
169
- fontSize: "0.875rem",
170
- }}
171
- >
172
- <div style={{ marginBottom: "0.5rem" }}>
173
- <svg
174
- width="48"
175
- height="48"
176
- viewBox="0 0 24 24"
177
- fill="none"
178
- stroke="currentColor"
179
- strokeWidth="1.5"
180
- strokeLinecap="round"
181
- strokeLinejoin="round"
182
- style={{ margin: "0 auto", opacity: 0.5 }}
183
- >
184
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
185
- </svg>
186
- </div>
187
- <p style={{ margin: 0 }}>No conversations yet</p>
188
- <p style={{ margin: "0.25rem 0 0 0", fontSize: "0.75rem" }}>
189
- Start a new chat to see your history here
190
- </p>
191
- </div>
192
- ) : (
193
- sessionList.map((session) => (
194
- <SessionListItem
195
- key={session.id}
196
- session={session}
197
- isActive={currentSessionId === session.id}
198
- onSelect={handleOnSessionSelect}
199
- onDelete={async () => {
200
- await props.onDeleteSession?.(session.id);
201
- refreshSessionList();
202
- if (session.id === currentSessionId) {
203
- setSearchParams((sp) => {
204
- const newSp = new URLSearchParams(sp);
205
- newSp.delete("session-id");
206
- return newSp;
207
- });
208
- }
209
- }}
210
- onEditTitle={async (_: string, newTitle: string) => {
211
- // Update the session title through storage strategy
212
- await props.storageStrategy.editSessionTitle({
213
- id: session.id,
214
- title: newTitle,
215
- });
216
- refreshSessionList();
217
- }}
218
- />
219
- ))
220
- )}
221
- </>
222
- )}
223
- </div>
224
- </div>
225
- );
226
- };
227
-
228
- export default AutoBeChatSidebar;
229
-
230
- /** Props for ConversationListItem component */
231
- export interface IConversationListItemProps {
232
- /** Conversation session data */
233
- session: IAutoBeAgentSession;
234
- /** Whether this conversation is currently active */
235
- isActive: boolean;
236
- /** Callback when conversation is selected */
237
- onSelect: (sessionId: string) => void;
238
- /** Callback when conversation should be deleted */
239
- onDelete: (sessionId: string) => void;
240
- /** Callback when conversation should be edited */
241
- onEditTitle: (sessionId: string, title: string) => void;
242
- }
243
-
244
- /**
245
- * Individual conversation list item component Displays conversation title,
246
- * metadata, and actions
247
- */
248
- export const SessionListItem = (props: IConversationListItemProps) => {
249
- const { session, isActive, onSelect, onDelete, onEditTitle } = props;
250
- const [isHovered, setIsHovered] = useState(false);
251
- const [isEditing, setIsEditing] = useState(false);
252
- const [editingTitle, setEditingTitle] = useState(session.title ?? "");
253
- const lastMessage = session.history.at(-1);
254
-
255
- //----
256
- // EVENT HANDLERS
257
- //----
258
- const handleStartEditing = () => {
259
- setIsEditing(true);
260
- setEditingTitle(session.title ?? "Untitled");
261
- };
262
-
263
- const handleSaveTitle = () => {
264
- const trimmedTitle = editingTitle.trim();
265
- if (trimmedTitle && trimmedTitle !== session.title) {
266
- onEditTitle?.(session.id, trimmedTitle);
267
- }
268
- setIsEditing(false);
269
- };
270
-
271
- const handleCancelEditing = () => {
272
- setIsEditing(false);
273
- setEditingTitle(session.title ?? "Untitled");
274
- };
275
-
276
- const handleKeyPress = (e: React.KeyboardEvent) => {
277
- if (e.key === "Enter") {
278
- handleSaveTitle();
279
- } else if (e.key === "Escape") {
280
- handleCancelEditing();
281
- }
282
- };
283
-
284
- return (
285
- <div
286
- style={{
287
- marginBottom: "0.5rem",
288
- borderRadius: "0.75rem",
289
- padding: "0.75rem",
290
- cursor: "pointer",
291
- backgroundColor: isActive
292
- ? "#eff6ff"
293
- : isHovered
294
- ? "#f9fafb"
295
- : "transparent",
296
- border: isActive ? "1px solid #dbeafe" : "1px solid transparent",
297
- transition: "all 0.2s ease",
298
- display: "flex",
299
- flexDirection: "column",
300
- gap: "0.25rem",
301
- position: "relative",
302
- }}
303
- onClick={() => onSelect(session.id)}
304
- onMouseEnter={() => setIsHovered(true)}
305
- onMouseLeave={() => setIsHovered(false)}
306
- >
307
- {/* Conversation title and action buttons */}
308
- <div
309
- style={{
310
- display: "flex",
311
- alignItems: "center",
312
- justifyContent: "space-between",
313
- gap: "0.5rem",
314
- fontSize: "0.875rem",
315
- fontWeight: "500",
316
- color: isActive ? "#1d4ed8" : "#1f2937",
317
- lineHeight: "1.25",
318
- paddingRight: "0.5rem", // Space for buttons
319
- }}
320
- >
321
- {/* Title section */}
322
- <div
323
- style={{
324
- flex: 1,
325
- display: "flex",
326
- alignItems: "center",
327
- minWidth: 0, // Allow shrinking
328
- }}
329
- >
330
- {isEditing ? (
331
- <input
332
- type="text"
333
- value={editingTitle}
334
- onChange={(e) => setEditingTitle(e.target.value)}
335
- onKeyDown={handleKeyPress}
336
- onBlur={handleSaveTitle}
337
- style={{
338
- flex: 1,
339
- border: "1px solid #d1d5db",
340
- borderRadius: "0.25rem",
341
- padding: "0.25rem 0.5rem",
342
- fontSize: "0.875rem",
343
- fontWeight: "500",
344
- color: isActive ? "#1d4ed8" : "#1f2937",
345
- backgroundColor: "#ffffff",
346
- outline: "none",
347
- boxShadow: "0 0 0 2px rgba(59, 130, 246, 0.25)",
348
- }}
349
- autoFocus
350
- onClick={(e) => e.stopPropagation()}
351
- />
352
- ) : (
353
- <span
354
- style={{
355
- overflow: "hidden",
356
- textOverflow: "ellipsis",
357
- whiteSpace: "nowrap",
358
- flex: 1,
359
- }}
360
- >
361
- {session.title ?? "Untitled"}
362
- </span>
363
- )}
364
- </div>
365
-
366
- {/* Action buttons group */}
367
- {!isEditing ? (
368
- <div
369
- style={{
370
- visibility: isHovered ? "visible" : "hidden",
371
- opacity: isHovered ? 1 : 0,
372
- transition: "opacity 0.2s ease",
373
- }}
374
- >
375
- <ActionButtonGroup
376
- onEdit={handleStartEditing}
377
- onDelete={onDelete ? () => onDelete(session.id) : undefined}
378
- />
379
- </div>
380
- ) : (
381
- <ActionButtonGroup
382
- onSave={handleSaveTitle}
383
- onCancel={handleCancelEditing}
384
- />
385
- )}
386
- </div>
387
-
388
- {/* Conversation metadata */}
389
- <div
390
- style={{
391
- display: "flex",
392
- justifyContent: "space-between",
393
- alignItems: "center",
394
- fontSize: "0.75rem",
395
- color: "#6b7280",
396
- }}
397
- >
398
- <span>
399
- {session.history.length > 0 && lastMessage !== undefined
400
- ? new Date(lastMessage.created_at).toLocaleDateString("en-US", {
401
- month: "short",
402
- day: "numeric",
403
- hour: "2-digit",
404
- minute: "2-digit",
405
- })
406
- : "No messages"}
407
- </span>
408
- {session.history.length > 0 && (
409
- <span>{session.history.length} messages</span>
410
- )}
411
- </div>
412
- </div>
413
- );
414
- };
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ import { useAutoBeAgentSessionList } from "../context/AutoBeAgentSessionList";
4
+ import { useSearchParams } from "../context/SearchParamsContext";
5
+ import {
6
+ IAutoBeAgentSession,
7
+ IAutoBeAgentSessionStorageStrategy,
8
+ } from "../structure";
9
+ import { ActionButtonGroup } from "./common/ActionButtonGroup";
10
+ import { CompactSessionList } from "./common/CompactSessionList";
11
+
12
+ /** Props interface for AutoBeChatSidebar component */
13
+ export interface IAutoBeChatSidebarProps {
14
+ storageStrategy: IAutoBeAgentSessionStorageStrategy;
15
+ /** Whether the sidebar is collapsed (true) or expanded (false) */
16
+ isCollapsed: boolean;
17
+ /** Function to toggle sidebar collapsed/expanded */
18
+ onToggle: () => void;
19
+ /** Custom className */
20
+ className?: string;
21
+ /** Function to select a session */
22
+ onSessionSelect?: (id: string) => Promise<void> | void;
23
+ /** Function to delete a session */
24
+ onDeleteSession?: (id: string) => Promise<void> | void;
25
+ }
26
+
27
+ const collapsedWidth = "60px";
28
+ const expandedWidth = "320px";
29
+ /** Beautiful and modern chat sidebar component as part of layout */
30
+ export const AutoBeChatSidebar = (props: IAutoBeChatSidebarProps) => {
31
+ const { sessionList, refreshSessionList } = useAutoBeAgentSessionList();
32
+ const { searchParams, setSearchParams } = useSearchParams();
33
+ const activeSessionId = searchParams.get("session-id") ?? null;
34
+ const [currentSessionId, setCurrentSessionId] = useState(
35
+ Array.isArray(activeSessionId) ? activeSessionId.at(0) : activeSessionId,
36
+ );
37
+
38
+ const handleOnSessionSelect = useCallback(
39
+ (sessionId: string) => {
40
+ props.onSessionSelect?.(sessionId);
41
+ setCurrentSessionId(sessionId);
42
+ },
43
+ [props.onSessionSelect, setSearchParams],
44
+ );
45
+
46
+ useEffect(() => {
47
+ setCurrentSessionId(activeSessionId);
48
+ }, [searchParams]);
49
+
50
+ return (
51
+ <div
52
+ className={props.className}
53
+ style={{
54
+ position: "relative",
55
+ height: "100%",
56
+ width: props.isCollapsed ? collapsedWidth : expandedWidth,
57
+ backgroundColor: "#ffffff",
58
+ borderRight: "1px solid #e5e7eb",
59
+ boxShadow: "2px 0 4px rgba(0, 0, 0, 0.05)",
60
+ transition: "width 0.3s ease",
61
+ display: "flex",
62
+ flexDirection: "column",
63
+ overflow: "hidden",
64
+ flexShrink: 0,
65
+ }}
66
+ >
67
+ {/* Header section */}
68
+ <div
69
+ style={{
70
+ padding: props.isCollapsed ? "1rem 0.75rem" : "1.5rem 1.25rem 1rem",
71
+ borderBottom: "1px solid #f3f4f6",
72
+ backgroundColor: "#fafafa",
73
+ transition: "padding 0.3s ease",
74
+ }}
75
+ >
76
+ {/* Toggle button and title */}
77
+ <div
78
+ style={{
79
+ display: "flex",
80
+ justifyContent: props.isCollapsed ? "center" : "space-between",
81
+ alignItems: "center",
82
+ marginBottom: props.isCollapsed ? "0" : "1rem",
83
+ transition: "all 0.3s ease",
84
+ }}
85
+ >
86
+ {!props.isCollapsed && (
87
+ <h2
88
+ style={{
89
+ fontSize: "1.25rem",
90
+ fontWeight: "600",
91
+ color: "#1f2937",
92
+ margin: 0,
93
+ opacity: props.isCollapsed ? 0 : 1,
94
+ transition: "opacity 0.3s ease",
95
+ }}
96
+ >
97
+ Chat History
98
+ </h2>
99
+ )}
100
+ <button
101
+ onClick={props.onToggle}
102
+ style={{
103
+ background: "none",
104
+ border: "none",
105
+ cursor: "pointer",
106
+ padding: "0.5rem",
107
+ borderRadius: "0.5rem",
108
+ display: "flex",
109
+ alignItems: "center",
110
+ justifyContent: "center",
111
+ transition: "background-color 0.2s ease",
112
+ }}
113
+ onMouseEnter={(e) => {
114
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
115
+ }}
116
+ onMouseLeave={(e) => {
117
+ e.currentTarget.style.backgroundColor = "transparent";
118
+ }}
119
+ title={props.isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
120
+ >
121
+ <svg
122
+ width="20"
123
+ height="20"
124
+ viewBox="0 0 24 24"
125
+ fill="none"
126
+ stroke="currentColor"
127
+ strokeWidth="2"
128
+ strokeLinecap="round"
129
+ strokeLinejoin="round"
130
+ style={{
131
+ transform: props.isCollapsed
132
+ ? "rotate(0deg)"
133
+ : "rotate(180deg)",
134
+ transition: "transform 0.3s ease",
135
+ }}
136
+ >
137
+ <path d="M15 18l-6-6 6-6" />
138
+ </svg>
139
+ </button>
140
+ </div>
141
+ </div>
142
+
143
+ {/* Conversations list */}
144
+ <div
145
+ style={{
146
+ flex: 1,
147
+ overflowY: props.isCollapsed ? "visible" : "auto",
148
+ padding: props.isCollapsed ? "0.25rem" : "0.5rem",
149
+ transition: "padding 0.3s ease",
150
+ }}
151
+ >
152
+ {props.isCollapsed ? (
153
+ // Collapsed state - show compact conversation indicators
154
+ <CompactSessionList
155
+ sessions={sessionList}
156
+ activeSessionId={currentSessionId}
157
+ maxItems={8}
158
+ onSessionSelect={handleOnSessionSelect}
159
+ />
160
+ ) : (
161
+ // Expanded state - show full conversation list
162
+ <>
163
+ {sessionList.length === 0 ? (
164
+ <div
165
+ style={{
166
+ padding: "2rem 1rem",
167
+ textAlign: "center",
168
+ color: "#6b7280",
169
+ fontSize: "0.875rem",
170
+ }}
171
+ >
172
+ <div style={{ marginBottom: "0.5rem" }}>
173
+ <svg
174
+ width="48"
175
+ height="48"
176
+ viewBox="0 0 24 24"
177
+ fill="none"
178
+ stroke="currentColor"
179
+ strokeWidth="1.5"
180
+ strokeLinecap="round"
181
+ strokeLinejoin="round"
182
+ style={{ margin: "0 auto", opacity: 0.5 }}
183
+ >
184
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
185
+ </svg>
186
+ </div>
187
+ <p style={{ margin: 0 }}>No conversations yet</p>
188
+ <p style={{ margin: "0.25rem 0 0 0", fontSize: "0.75rem" }}>
189
+ Start a new chat to see your history here
190
+ </p>
191
+ </div>
192
+ ) : (
193
+ sessionList.map((session) => (
194
+ <SessionListItem
195
+ key={session.id}
196
+ session={session}
197
+ isActive={currentSessionId === session.id}
198
+ onSelect={handleOnSessionSelect}
199
+ onDelete={async () => {
200
+ await props.onDeleteSession?.(session.id);
201
+ refreshSessionList();
202
+ if (session.id === currentSessionId) {
203
+ setSearchParams((sp) => {
204
+ const newSp = new URLSearchParams(sp);
205
+ newSp.delete("session-id");
206
+ return newSp;
207
+ });
208
+ }
209
+ }}
210
+ onEditTitle={async (_: string, newTitle: string) => {
211
+ // Update the session title through storage strategy
212
+ await props.storageStrategy.editSessionTitle({
213
+ id: session.id,
214
+ title: newTitle,
215
+ });
216
+ refreshSessionList();
217
+ }}
218
+ />
219
+ ))
220
+ )}
221
+ </>
222
+ )}
223
+ </div>
224
+ </div>
225
+ );
226
+ };
227
+
228
+ export default AutoBeChatSidebar;
229
+
230
+ /** Props for ConversationListItem component */
231
+ export interface IConversationListItemProps {
232
+ /** Conversation session data */
233
+ session: IAutoBeAgentSession;
234
+ /** Whether this conversation is currently active */
235
+ isActive: boolean;
236
+ /** Callback when conversation is selected */
237
+ onSelect: (sessionId: string) => void;
238
+ /** Callback when conversation should be deleted */
239
+ onDelete: (sessionId: string) => void;
240
+ /** Callback when conversation should be edited */
241
+ onEditTitle: (sessionId: string, title: string) => void;
242
+ }
243
+
244
+ /**
245
+ * Individual conversation list item component Displays conversation title,
246
+ * metadata, and actions
247
+ */
248
+ export const SessionListItem = (props: IConversationListItemProps) => {
249
+ const { session, isActive, onSelect, onDelete, onEditTitle } = props;
250
+ const [isHovered, setIsHovered] = useState(false);
251
+ const [isEditing, setIsEditing] = useState(false);
252
+ const [editingTitle, setEditingTitle] = useState(session.title ?? "");
253
+ const lastMessage = session.history.at(-1);
254
+
255
+ //----
256
+ // EVENT HANDLERS
257
+ //----
258
+ const handleStartEditing = () => {
259
+ setIsEditing(true);
260
+ setEditingTitle(session.title ?? "Untitled");
261
+ };
262
+
263
+ const handleSaveTitle = () => {
264
+ const trimmedTitle = editingTitle.trim();
265
+ if (trimmedTitle && trimmedTitle !== session.title) {
266
+ onEditTitle?.(session.id, trimmedTitle);
267
+ }
268
+ setIsEditing(false);
269
+ };
270
+
271
+ const handleCancelEditing = () => {
272
+ setIsEditing(false);
273
+ setEditingTitle(session.title ?? "Untitled");
274
+ };
275
+
276
+ const handleKeyPress = (e: React.KeyboardEvent) => {
277
+ if (e.key === "Enter") {
278
+ handleSaveTitle();
279
+ } else if (e.key === "Escape") {
280
+ handleCancelEditing();
281
+ }
282
+ };
283
+
284
+ return (
285
+ <div
286
+ style={{
287
+ marginBottom: "0.5rem",
288
+ borderRadius: "0.75rem",
289
+ padding: "0.75rem",
290
+ cursor: "pointer",
291
+ backgroundColor: isActive
292
+ ? "#eff6ff"
293
+ : isHovered
294
+ ? "#f9fafb"
295
+ : "transparent",
296
+ border: isActive ? "1px solid #dbeafe" : "1px solid transparent",
297
+ transition: "all 0.2s ease",
298
+ display: "flex",
299
+ flexDirection: "column",
300
+ gap: "0.25rem",
301
+ position: "relative",
302
+ }}
303
+ onClick={() => onSelect(session.id)}
304
+ onMouseEnter={() => setIsHovered(true)}
305
+ onMouseLeave={() => setIsHovered(false)}
306
+ >
307
+ {/* Conversation title and action buttons */}
308
+ <div
309
+ style={{
310
+ display: "flex",
311
+ alignItems: "center",
312
+ justifyContent: "space-between",
313
+ gap: "0.5rem",
314
+ fontSize: "0.875rem",
315
+ fontWeight: "500",
316
+ color: isActive ? "#1d4ed8" : "#1f2937",
317
+ lineHeight: "1.25",
318
+ paddingRight: "0.5rem", // Space for buttons
319
+ }}
320
+ >
321
+ {/* Title section */}
322
+ <div
323
+ style={{
324
+ flex: 1,
325
+ display: "flex",
326
+ alignItems: "center",
327
+ minWidth: 0, // Allow shrinking
328
+ }}
329
+ >
330
+ {isEditing ? (
331
+ <input
332
+ type="text"
333
+ value={editingTitle}
334
+ onChange={(e) => setEditingTitle(e.target.value)}
335
+ onKeyDown={handleKeyPress}
336
+ onBlur={handleSaveTitle}
337
+ style={{
338
+ flex: 1,
339
+ border: "1px solid #d1d5db",
340
+ borderRadius: "0.25rem",
341
+ padding: "0.25rem 0.5rem",
342
+ fontSize: "0.875rem",
343
+ fontWeight: "500",
344
+ color: isActive ? "#1d4ed8" : "#1f2937",
345
+ backgroundColor: "#ffffff",
346
+ outline: "none",
347
+ boxShadow: "0 0 0 2px rgba(59, 130, 246, 0.25)",
348
+ }}
349
+ autoFocus
350
+ onClick={(e) => e.stopPropagation()}
351
+ />
352
+ ) : (
353
+ <span
354
+ style={{
355
+ overflow: "hidden",
356
+ textOverflow: "ellipsis",
357
+ whiteSpace: "nowrap",
358
+ flex: 1,
359
+ }}
360
+ >
361
+ {session.title ?? "Untitled"}
362
+ </span>
363
+ )}
364
+ </div>
365
+
366
+ {/* Action buttons group */}
367
+ {!isEditing ? (
368
+ <div
369
+ style={{
370
+ visibility: isHovered ? "visible" : "hidden",
371
+ opacity: isHovered ? 1 : 0,
372
+ transition: "opacity 0.2s ease",
373
+ }}
374
+ >
375
+ <ActionButtonGroup
376
+ onEdit={handleStartEditing}
377
+ onDelete={onDelete ? () => onDelete(session.id) : undefined}
378
+ />
379
+ </div>
380
+ ) : (
381
+ <ActionButtonGroup
382
+ onSave={handleSaveTitle}
383
+ onCancel={handleCancelEditing}
384
+ />
385
+ )}
386
+ </div>
387
+
388
+ {/* Conversation metadata */}
389
+ <div
390
+ style={{
391
+ display: "flex",
392
+ justifyContent: "space-between",
393
+ alignItems: "center",
394
+ fontSize: "0.75rem",
395
+ color: "#6b7280",
396
+ }}
397
+ >
398
+ <span>
399
+ {session.history.length > 0 && lastMessage !== undefined
400
+ ? new Date(lastMessage.created_at).toLocaleDateString("en-US", {
401
+ month: "short",
402
+ day: "numeric",
403
+ hour: "2-digit",
404
+ minute: "2-digit",
405
+ })
406
+ : "No messages"}
407
+ </span>
408
+ {session.history.length > 0 && (
409
+ <span>{session.history.length} messages</span>
410
+ )}
411
+ </div>
412
+ </div>
413
+ );
414
+ };