added cors support and started chat client

This commit is contained in:
Storme-bit
2026-04-06 03:25:25 -07:00
parent 461438e81b
commit 1e2ce7a761
16 changed files with 2610 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
import React, { useEffect, useRef } from 'react';
import MessageBubble from './MessageBubble';
export default function ChatWindow({ messages, loadingHistory, streaming, onSendMessage, onCancel, activeSession }) {
const bottomRef = useRef(null);
const inputRef = useRef(null);
const [input, setInput] = React.useState('');
// Auto-scroll to bottom when messages change
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
function handleSend() {
const text = input.trim();
if (!text || streaming) return;
setInput('');
onSendMessage(text);
}
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}
return (
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
background: 'var(--bg-base)',
}}>
{/* Header */}
<div style={{
height: 'var(--header-height)',
borderBottom: '1px solid var(--border)',
display: 'flex',
alignItems: 'center',
padding: '0 20px',
background: 'var(--bg-surface)',
flexShrink: 0,
}}>
<span style={{ color: 'var(--text-secondary)', fontSize: '13px' }}>
{activeSession ? activeSession.external_id : 'No session selected'}
</span>
</div>
{/* Message thread */}
<div style={{
flex: 1,
overflowY: 'auto',
padding: '20px 0',
}}>
{!activeSession && (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: 'var(--text-muted)',
gap: '12px',
}}>
<div style={{ fontSize: '32px', opacity: 0.4 }}></div>
<p style={{ fontSize: '14px' }}>Select a session or start a new chat</p>
</div>
)}
{loadingHistory && (
<div style={{
display: 'flex',
justifyContent: 'center',
padding: '40px',
color: 'var(--text-muted)',
fontSize: '13px',
}}>
Loading history...
</div>
)}
{!loadingHistory && messages.map(msg => (
<MessageBubble key={msg.id} message={msg} />
))}
<div ref={bottomRef} />
</div>
{/* Input bar */}
<div style={{
borderTop: '1px solid var(--border)',
padding: '12px 16px',
background: 'var(--bg-surface)',
flexShrink: 0,
}}>
<div style={{
display: 'flex',
gap: '10px',
alignItems: 'flex-end',
background: 'var(--bg-elevated)',
border: '1px solid var(--border)',
borderRadius: '12px',
padding: '8px 12px',
}}>
<textarea
ref={inputRef}
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
disabled={!activeSession}
placeholder={activeSession ? 'Message NexusAI...' : 'Select a session to start chatting'}
rows={1}
style={{
flex: 1,
background: 'transparent',
border: 'none',
outline: 'none',
color: 'var(--text-primary)',
fontSize: '14px',
lineHeight: '1.6',
resize: 'none',
fontFamily: 'inherit',
maxHeight: '120px',
overflowY: 'auto',
}}
onInput={e => {
// Auto-grow textarea
e.target.style.height = 'auto';
e.target.style.height = `${e.target.scrollHeight}px`;
}}
/>
{streaming ? (
<button onClick={onCancel} style={{
background: 'var(--text-muted)',
border: 'none',
borderRadius: '8px',
width: '32px',
height: '32px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
color: 'white',
fontSize: '12px',
}}></button>
) : (
<button
onClick={handleSend}
disabled={!activeSession || !input.trim()}
style={{
background: activeSession && input.trim() ? 'var(--accent)' : 'var(--bg-elevated)',
border: '1px solid var(--border)',
borderRadius: '8px',
width: '32px',
height: '32px',
cursor: activeSession && input.trim() ? 'pointer' : 'default',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
color: activeSession && input.trim() ? 'white' : 'var(--text-muted)',
fontSize: '16px',
transition: 'background 0.15s',
}}></button>
)}
</div>
<p style={{
fontSize: '11px',
color: 'var(--text-muted)',
textAlign: 'center',
marginTop: '8px',
}}>
Enter to send · Shift+Enter for new line
</p>
</div>
</div>
);
}