feat(canvas): Add message input to ChatPanel with Claude integration
- Add textarea input with Enter-to-send functionality to ChatPanel - Pass sendMessage and isConnected props from AtomizerCanvas - Add disabled states during thinking/disconnected states - Use Lucide Send icon for submit button - Show connection status with Sparkles welcome icon Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ function CanvasFlow() {
|
||||
executeIntent,
|
||||
validateIntent,
|
||||
analyzeIntent,
|
||||
sendMessage,
|
||||
} = useCanvasChat({
|
||||
onError: (error) => console.error('Canvas chat error:', error),
|
||||
});
|
||||
@@ -213,6 +214,8 @@ function CanvasFlow() {
|
||||
<ChatPanel
|
||||
messages={messages}
|
||||
isThinking={isThinking || isExecuting}
|
||||
onSendMessage={sendMessage}
|
||||
isConnected={isConnected}
|
||||
/>
|
||||
</div>
|
||||
) : selectedNode ? (
|
||||
|
||||
@@ -1,48 +1,96 @@
|
||||
/**
|
||||
* Chat Panel for Canvas - Displays messages from Claude
|
||||
* Chat Panel for Canvas - Displays messages from Claude with input
|
||||
*/
|
||||
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { useRef, useEffect, useState, KeyboardEvent } from 'react';
|
||||
import { Send, Sparkles } from 'lucide-react';
|
||||
import { Message, ChatMessage } from '../../chat/ChatMessage';
|
||||
import { ThinkingIndicator } from '../../chat/ThinkingIndicator';
|
||||
|
||||
interface ChatPanelProps {
|
||||
messages: Message[];
|
||||
isThinking: boolean;
|
||||
onSendMessage?: (message: string) => void;
|
||||
isConnected?: boolean;
|
||||
}
|
||||
|
||||
export function ChatPanel({ messages, isThinking }: ChatPanelProps) {
|
||||
export function ChatPanel({ messages, isThinking, onSendMessage, isConnected = true }: ChatPanelProps) {
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
// Auto-scroll to bottom when new messages arrive
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages, isThinking]);
|
||||
|
||||
const handleSend = () => {
|
||||
if (input.trim() && onSendMessage && !isThinking) {
|
||||
onSendMessage(input.trim());
|
||||
setInput('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-dark-900">
|
||||
{/* Welcome message if no messages */}
|
||||
{messages.length === 0 && !isThinking && (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 rounded-xl bg-primary-500/20 flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl">🧠</span>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Messages */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-dark-900">
|
||||
{/* Welcome message if no messages */}
|
||||
{messages.length === 0 && !isThinking && (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 rounded-xl bg-primary-500/20 flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles size={24} className="text-primary-400" />
|
||||
</div>
|
||||
<p className="text-dark-400 text-sm max-w-xs mx-auto">
|
||||
Use <strong className="text-white">Validate</strong>, <strong className="text-white">Analyze</strong>, or <strong className="text-white">Execute</strong> to interact with Claude about your optimization workflow.
|
||||
</p>
|
||||
<p className="text-dark-500 text-xs mt-4">
|
||||
Or type a message below to ask questions.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages */}
|
||||
{messages.map((message) => (
|
||||
<ChatMessage key={message.id} message={message} />
|
||||
))}
|
||||
|
||||
{/* Thinking indicator */}
|
||||
{isThinking && <ThinkingIndicator />}
|
||||
|
||||
{/* Scroll anchor */}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
{onSendMessage && (
|
||||
<div className="p-3 border-t border-dark-700 bg-dark-850">
|
||||
<div className="flex gap-2">
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={isConnected ? "Ask about your optimization..." : "Connecting..."}
|
||||
disabled={isThinking || !isConnected}
|
||||
rows={1}
|
||||
className="flex-1 px-3 py-2 bg-dark-800 border border-dark-600 text-white placeholder-dark-500 rounded-lg resize-none focus:border-primary-500 focus:outline-none transition-colors disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={!input.trim() || isThinking || !isConnected}
|
||||
className="px-3 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<Send size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-dark-400 text-sm max-w-xs mx-auto">
|
||||
Use <strong className="text-white">Validate</strong>, <strong className="text-white">Analyze</strong>, or <strong className="text-white">Execute</strong> to interact with Claude about your optimization workflow.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages */}
|
||||
{messages.map((message) => (
|
||||
<ChatMessage key={message.id} message={message} />
|
||||
))}
|
||||
|
||||
{/* Thinking indicator */}
|
||||
{isThinking && <ThinkingIndicator />}
|
||||
|
||||
{/* Scroll anchor */}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user