added cors support and started chat client
This commit is contained in:
161
packages/chat-client/src/components/SessionList.jsx
Normal file
161
packages/chat-client/src/components/SessionList.jsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function SessionList({ sessions, activeSession, onSelectSession, onNewChat, isOpen, onToggle }) {
|
||||
function formatDate(ts) {
|
||||
if (!ts) return '';
|
||||
const date = new Date(ts * 1000);
|
||||
const now = new Date();
|
||||
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
||||
if (diffDays === 0) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
if (diffDays === 1) return 'Yesterday';
|
||||
if (diffDays < 7) return date.toLocaleDateString([], { weekday: 'long' });
|
||||
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function getPreview(session) {
|
||||
if (session.isNew) return 'New conversation';
|
||||
return session.external_id;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: isOpen ? 'var(--sidebar-width)' : '56px',
|
||||
flexShrink: 0,
|
||||
background: 'var(--bg-surface)',
|
||||
borderRight: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'width 0.2s ease',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
|
||||
{/* Header */}
|
||||
<div style={{
|
||||
height: 'var(--header-height)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: isOpen ? 'space-between' : 'center',
|
||||
padding: isOpen ? '0 12px 0 16px' : '0',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{isOpen && (
|
||||
<span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-secondary)' }}>
|
||||
Conversations
|
||||
</span>
|
||||
)}
|
||||
<button onClick={onToggle} style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
padding: '6px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '16px',
|
||||
lineHeight: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
{isOpen ? '◀' : '▶'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* New chat button */}
|
||||
<div style={{ padding: isOpen ? '12px' : '12px 8px', flexShrink: 0 }}>
|
||||
<button onClick={onNewChat} style={{
|
||||
width: '100%',
|
||||
padding: isOpen ? '8px 12px' : '8px',
|
||||
background: 'var(--accent)',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
color: 'white',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: isOpen ? 'flex-start' : 'center',
|
||||
gap: '8px',
|
||||
transition: 'background 0.15s',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.background = 'var(--accent-hover)'}
|
||||
onMouseLeave={e => e.currentTarget.style.background = 'var(--accent)'}
|
||||
>
|
||||
<span style={{ fontSize: '18px', lineHeight: 1, flexShrink: 0 }}>+</span>
|
||||
{isOpen && <span>New Chat</span>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Session list */}
|
||||
<div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
{isOpen && sessions.map(session => {
|
||||
const isActive = activeSession?.external_id === session.external_id;
|
||||
return (
|
||||
<button
|
||||
key={session.external_id}
|
||||
onClick={() => onSelectSession(session)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 16px',
|
||||
background: isActive ? 'var(--bg-elevated)' : 'transparent',
|
||||
border: 'none',
|
||||
borderLeft: isActive ? '2px solid var(--accent)' : '2px solid transparent',
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '3px',
|
||||
transition: 'background 0.1s',
|
||||
}}
|
||||
onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'var(--bg-elevated)'; }}
|
||||
onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'transparent'; }}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}>
|
||||
<span style={{
|
||||
fontSize: '13px',
|
||||
color: isActive ? 'var(--text-primary)' : 'var(--text-secondary)',
|
||||
fontWeight: isActive ? 500 : 400,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
flex: 1,
|
||||
}}>
|
||||
{getPreview(session)}
|
||||
</span>
|
||||
<span style={{ fontSize: '11px', color: 'var(--text-muted)', flexShrink: 0 }}>
|
||||
{formatDate(session.updated_at)}
|
||||
</span>
|
||||
</div>
|
||||
{session.isNew && (
|
||||
<span style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--accent)',
|
||||
fontStyle: 'italic',
|
||||
}}>Unsaved</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
{isOpen && sessions.length === 0 && (
|
||||
<div style={{
|
||||
padding: '24px 16px',
|
||||
color: 'var(--text-muted)',
|
||||
fontSize: '13px',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
No conversations yet
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user