diff --git a/packages/chat-client/src/App.jsx b/packages/chat-client/src/App.jsx index 04e61e8..0caca45 100644 --- a/packages/chat-client/src/App.jsx +++ b/packages/chat-client/src/App.jsx @@ -48,6 +48,7 @@ export default function App() { onNewChat={createSession} isOpen={leftOpen} onToggle={() => setLeftOpen(o => !o)} + onSessionsChange={refreshSessions} /> + function getPreview(session) { + if (session.isNew) return 'New conversation'; + return session.name || session.external_id; + } - {/* Header */} -
- {isOpen && Conversations} - -
+ async function handleRename(session, name) { + try { + await renameSession(session.external_id, name); + onSessionsChange(); + } catch (err) { + console.error('[SessionList] Rename failed:', err.message); + } + } - {/* New chat button */} -
- -
+ async function handleDelete(session) { + try { + await deleteSession(session.external_id); + onSessionsChange(); + } catch (err) { + console.error('[SessionList] Delete failed:', err.message); + } + } - {/* Session list */} -
- {isOpen && sessions.map(session => { - const isActive = activeSession?.external_id === session.external_id; - return ( - +
+ + {/* New chat button */} +
+
- {session.isNew && ( - Unsaved - )} - - ); - })} + + + {isOpen && New Chat} + + - {isOpen && sessions.length === 0 && ( -
- No conversations yet -
+ {/* Session list */} +
+ {isOpen && sessions.map(session => { + const isActive = activeSession?.external_id === session.external_id; + const isHovered = hoveredId === session.external_id; + + return ( +
setHoveredId(session.external_id)} + onMouseLeave={() => setHoveredId(null)} + onContextMenu={e => !session.isNew && openMenu(e, session)} + style={{ + position: 'relative', + background: isActive ? 'var(--bg-elevated)' : isHovered ? 'var(--bg-elevated)' : 'transparent', + borderLeft: isActive ? '2px solid var(--accent)' : '2px solid transparent', + transition: 'background 0.1s', + }} + > + + +
+ ) : ( + + {formatDate(session.updated_at)} + + )} +
+ {session.isNew && ( + Unsaved + )} + + + ); + })} + + {isOpen && sessions.length === 0 && ( +
+ No conversations yet +
+ )} + + + + {/* Context menu */} + {menu && ( +
e.stopPropagation()} + style={{ + position: 'fixed', + top: menu.y, + left: menu.x, + background: 'var(--bg-elevated)', + border: '1px solid var(--border)', + borderRadius: 'var(--radius-md)', + padding: '4px', + zIndex: 50, + minWidth: '140px', + }} + > + + +
)} - - - ); + + {/* Rename modal */} + {modalSession && ( + setModalSession(null)} + /> + )} + + ); } \ No newline at end of file diff --git a/packages/chat-client/src/components/SessionModal.jsx b/packages/chat-client/src/components/SessionModal.jsx index e69de29..f6ac3ac 100644 --- a/packages/chat-client/src/components/SessionModal.jsx +++ b/packages/chat-client/src/components/SessionModal.jsx @@ -0,0 +1,108 @@ +import React, {useState, useEffect, useRef} from "react"; + +export default function SessionModal({ session, onRename, onClose}) { + const [name, setName] = useState(session?.name || ''); + const inputRef = useRef(null) + + //Focus input when modal opens + useEffect(() => { + inputRef.current?.focus(); + inputRef.current?.select(); + }, []); + + function handleSubmit() { + const trimmed = name.trim(); + if (!trimmed) return; + + onRename(session, trimmed); + onClose(); + } + + function handleKeyDown(e) { + if (e.key === 'Enter') handleSubmit(); + if(e.key === 'Escape') onClose(); + + } + + if (!session) return null; + return ( + //Backdrop +
+ {/* Modal - stop click propagating to backdrop */} +
e.stopPropagation()} + style={{ + background: 'var(--bg-surface)', + border: '1px solid var(--border)', + borderRadius: 'var(--radius-lg)', + padding: '24px', + width: '360px', + display: 'flex', + flexDirection: 'column', + gap: '16px', + }} + > + {/* Modal Header*/} +

+ Session Settings +

+ + {/* Modal Session name input*/} +
+ + setName(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Enter session name..." + style={{ + background: 'var(--bg-elevated)', + border: '1px solid var(--border)', + borderRadius: 'var(--radius-md)', + padding: '8px 12px', + color: 'var(--text-primary)', + fontSize: '14px', + outline: 'none', + width: '100%', + }} + /> +
+ + {/* Modal Buttons*/} +
+ + +
+ +
+ +
+ + + + ) +} \ No newline at end of file