diff --git a/src/App.tsx b/src/App.tsx index 000674b..c6d0294 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,7 +33,28 @@ export default function App() { backgroundColor: 'var(--bg-primary)', color: 'var(--text-secondary)' }}> - Loading... +
+
+ + + Loading... +
) } @@ -43,17 +64,14 @@ export default function App() { {/* Menu Button */} - {/* Messages */}
{!session ? (
- Select or create a session to start chatting +
đŸ’Ŧ
+

+ Welcome to Light Chat +

+

Open the menu (☰) to create a session

) : session.messages.length === 0 ? (
- Start the conversation by sending a message +
👋
+

+ Start the conversation +

+

Type your message below to begin chatting

) : ( session.messages.map((msg, index) => ( @@ -164,44 +264,135 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) { key={index} style={{ display: 'flex', - flexDirection: msg.role === 'user' ? 'row' : 'row', - justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' + justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start', + animation: 'fadeIn 0.3s ease-out' }} >
- {msg.role === 'user' ? 'You' : 'Assistant'} + {msg.role === 'user' ? '👤 You' : '🤖 Assistant'}
-
+
{msg.content}
)) )} - {isLoading && ( -
+ + {/* Streaming message */} + {streamingContent && ( +
+
+
+ 🤖 Assistant + +
+
+ {streamingContent} +
+
+
+ )} + + {/* Loading indicator (non-streaming) */} + {isLoading && !streamingContent && ( +
+ Thinking...
)} + + {/* Error message */} {error && ( -
- Error: {error} +
+ âš ī¸ {error}
)}
@@ -211,24 +402,45 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
setInput(e.target.value)} - placeholder={config.endpoint ? "Type your message..." : "Configure API endpoint first"} + placeholder={config.endpoint ? "Type your message..." : "Configure API endpoint in menu (☰) first"} disabled={isLoading || !session} - style={{ flex: 1 }} + style={{ + flex: 1, + padding: '14px 18px', + fontSize: '15px' + }} /> - + + {isLoading ? ( + + ) : ( + + )}
) diff --git a/src/components/SessionDrawer.test.tsx b/src/components/SessionDrawer.test.tsx index 7af5b4f..1014c51 100644 --- a/src/components/SessionDrawer.test.tsx +++ b/src/components/SessionDrawer.test.tsx @@ -12,7 +12,7 @@ describe('SessionDrawer', () => { onSelectSession: vi.fn(), onCreateSession: vi.fn(), onDeleteSession: vi.fn(), - config: { endpoint: '', systemPrompt: '', model: 'local-swarm' } as Config, + config: { endpoint: '', systemPrompt: '', model: 'local-swarm', streaming: false } as Config, onUpdateConfig: vi.fn() } @@ -22,7 +22,7 @@ describe('SessionDrawer', () => { it('should render when open', () => { render() - expect(screen.getByText(/Sessions/)).toBeInTheDocument() + expect(screen.getByText(/Your Sessions/)).toBeInTheDocument() expect(screen.getByRole('button', { name: /New Session/i })).toBeInTheDocument() }) @@ -71,11 +71,11 @@ describe('SessionDrawer', () => { it('should call onUpdateConfig when save config clicked', () => { render() - const endpointInput = screen.getByPlaceholderText(/openai.com/i) + const endpointInput = screen.getByPlaceholderText(/192.168/i) fireEvent.change(endpointInput, { target: { value: 'https://new.com' } }) fireEvent.click(screen.getByRole('button', { name: /Save Config/i })) - expect(defaultProps.onUpdateConfig).toHaveBeenCalledWith({ endpoint: 'https://new.com', systemPrompt: '', model: 'local-swarm' }) + expect(defaultProps.onUpdateConfig).toHaveBeenCalledWith({ endpoint: 'https://new.com', systemPrompt: '', model: 'local-swarm', streaming: false }) }) }) diff --git a/src/components/SessionDrawer.tsx b/src/components/SessionDrawer.tsx index 3c5092f..784ae93 100644 --- a/src/components/SessionDrawer.tsx +++ b/src/components/SessionDrawer.tsx @@ -29,7 +29,7 @@ export function SessionDrawer({ }: SessionDrawerProps) { const [localConfig, setLocalConfig] = useState(config) - const handleConfigChange = (field: keyof Config, value: string) => { + const handleConfigChange = (field: keyof Config, value: string | boolean) => { setLocalConfig(prev => ({ ...prev, [field]: value })) } @@ -43,6 +43,15 @@ export function SessionDrawer({ } } + const formatDate = (timestamp: number) => { + return new Date(timestamp).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + } + return ( <> {/* Overlay */} @@ -54,7 +63,8 @@ export function SessionDrawer({ left: 0, right: 0, bottom: 0, - backgroundColor: 'rgba(0,0,0,0.5)', + backgroundColor: 'rgba(0,0,0,0.6)', + backdropFilter: 'blur(4px)', zIndex: 999 }} onClick={onClose} @@ -68,51 +78,90 @@ export function SessionDrawer({ top: 0, left: 0, bottom: 0, - width: '300px', + width: '340px', backgroundColor: 'var(--bg-secondary)', borderRight: '1px solid var(--border)', transform: isOpen ? 'translateX(0)' : 'translateX(-100%)', - transition: 'transform 0.3s ease', + transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)', zIndex: 1000, display: 'flex', flexDirection: 'column', - padding: '16px' + boxShadow: isOpen ? 'var(--shadow-lg)' : 'none' }} onKeyDown={handleKeyDown} tabIndex={isOpen ? 0 : -1} > -
-

Sessions

+ {/* Header */} +
+

+ đŸ’Ŧ Sessions +

{/* Config Section */} -
-

+
+

API Configuration

-
-