Spaces:
Sleeping
Sleeping
File size: 3,411 Bytes
ef1f403 95d1c6b b23f51c 2e9dd8e ef1f403 b23f51c ef1f403 b23f51c ef1f403 95d1c6b cdcf600 ef1f403 2e9dd8e cdcf600 2e9dd8e cdcf600 b23f51c ef1f403 b23f51c ef1f403 95d1c6b 2c333c8 ef1f403 2e9dd8e cdcf600 ef1f403 b23f51c ef1f403 7b9ee3e b23f51c ef1f403 95d1c6b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | import { useState, useRef, useEffect } from 'react';
import './ChatPanel.css';
/**
* ChatPanel
*
* Layer 3: Interaction (Utility Layer)
* Handles live conversation and input.
*/
function ChatPanel({ messages, onSendMessage, onNewChat, userAvatar, isTyping, onInputStateChange }) {
const [input, setInput] = useState('');
const scrollRef = useRef(null);
// Auto-scroll to bottom on new messages
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, isTyping]);
const handleInputChange = (e) => {
const val = e.target.value;
setInput(val);
if (onInputStateChange) {
onInputStateChange(val.length > 0);
}
};
const handleSubmit = (e) => {
e.preventDefault();
if (!input.trim() || isTyping) return;
onSendMessage(input);
setInput('');
if (onInputStateChange) onInputStateChange(false);
};
return (
<div className="chat-interface">
<div className="chat-messages" ref={scrollRef}>
{messages.length === 0 && (
<div className="welcome-state">
<div className="welcome-icon">
<span className="material-icons">lens_blur</span>
</div>
<h3>Neural Core Initialized.</h3>
<p>Feed me a thought to begin building your neural pathways.</p>
</div>
)}
{messages.map((msg, i) => (
<div key={i} className={`chat-bubble-group ${msg.role}`}>
<div className="chat-avatar">
{msg.role === 'user' ? (
<img src={userAvatar} alt="User" />
) : (
<span className="material-icons">lens_blur</span>
)}
</div>
<div className="bubble-body">
<div className="bubble-meta">
<strong>{msg.role === 'user' ? 'You' : 'Soma'}</strong>
</div>
<div className="bubble-text">{msg.content}</div>
<div className="bubble-time">{msg.timestamp}</div>
</div>
</div>
))}
{isTyping && messages.length > 0 && messages[messages.length - 1].role === 'user' && (
<div className="chat-bubble-group soma typing">
<div className="chat-avatar">
<span className="material-icons">lens_blur</span>
</div>
<div className="bubble-body">
<div className="typing-dots">
<span></span><span></span><span></span>
</div>
</div>
</div>
)}
</div>
<form className="chat-input-row" onSubmit={handleSubmit}>
<button
className="new-chat-btn"
type="button"
onClick={onNewChat}
title="New Chat"
disabled={isTyping}
>
<span className="material-icons">add</span>
</button>
<div className="input-field-wrap">
<input
type="text"
placeholder="Message Soma..."
value={input}
onChange={handleInputChange}
disabled={isTyping}
/>
</div>
<button className="send-btn" type="submit" disabled={!input.trim() || isTyping}>
<span className="material-icons">north</span>
</button>
</form>
</div>
);
}
export default ChatPanel;
|