Spaces:
Running
Running
File size: 3,411 Bytes
39e9c8d a9df765 035ec20 adfa6d0 39e9c8d 035ec20 39e9c8d 035ec20 39e9c8d a9df765 6e94481 39e9c8d adfa6d0 6e94481 adfa6d0 6e94481 035ec20 39e9c8d 035ec20 39e9c8d a9df765 9ebaf61 39e9c8d adfa6d0 6e94481 39e9c8d 035ec20 39e9c8d a7e4d75 035ec20 39e9c8d a9df765 | 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;
|