Hamza4100's picture
Upload 33 files
bbee504 verified
/**
* ChatWindow Component
*
* Main chat interface component that handles:
* - Message display
* - User input
* - Streaming responses from A2UI protocol
*/
import React, { useEffect, useRef, useState } from "react";
import { A2UIMessage, MessageType, Tool } from "../types/a2ui";
import { streamChat, checkHealth, getTools, clearHistory } from "../utils/api";
import MessageBubble from "./MessageBubble";
import "./ChatWindow.css";
interface ChatMessageDisplay extends A2UIMessage {
id: string;
timestamp: string;
}
const ChatWindow: React.FC = () => {
const [messages, setMessages] = useState<ChatMessageDisplay[]>([]);
const [inputValue, setInputValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [tools, setTools] = useState<Tool[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
// Check backend health on mount
useEffect(() => {
const checkConnection = async () => {
const healthy = await checkHealth();
setIsConnected(healthy);
if (healthy) {
const availableTools = await getTools();
setTools(availableTools);
}
};
checkConnection();
// Recheck every 30 seconds
const interval = setInterval(checkConnection, 30000);
return () => clearInterval(interval);
}, []);
// Scroll to bottom when new messages arrive
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!inputValue.trim()) return;
if (!isConnected) {
setError("Not connected to backend. Please check your connection.");
return;
}
const userMessage = inputValue.trim();
setInputValue("");
setIsLoading(true);
setError(null);
try {
// Add user message to display
setMessages((prev) => [
...prev,
{
type: MessageType.USER_MESSAGE,
content: userMessage,
id: `msg-${Date.now()}`,
timestamp: new Date().toISOString(),
},
]);
// Stream response from backend
await streamChat(
userMessage,
(message: A2UIMessage) => {
setMessages((prev) => [
...prev,
{
...message,
id: `msg-${Date.now()}-${Math.random()}`,
timestamp: message.timestamp || new Date().toISOString(),
},
]);
},
(error: Error) => {
setError(`Error: ${error.message}`);
}
);
} catch (err) {
setError(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
} finally {
setIsLoading(false);
}
};
const handleClearHistory = async () => {
if (window.confirm("Are you sure you want to clear chat history?")) {
const success = await clearHistory();
if (success) {
setMessages([]);
} else {
setError("Failed to clear history");
}
}
};
return (
<div className="chat-window">
{/* Header */}
<div className="chat-header">
<h1>AI Agent Chat</h1>
<div className="header-info">
<span className={`status-indicator ${isConnected ? "connected" : "disconnected"}`}>
{isConnected ? "●" : "●"} {isConnected ? "Connected" : "Disconnected"}
</span>
{tools.length > 0 && (
<span className="tools-indicator">{tools.length} tools available</span>
)}
</div>
</div>
{/* Messages area */}
<div className="messages-container">
{messages.length === 0 && (
<div className="empty-state">
<h2>Welcome to AI Agent Chat</h2>
<p>
{isConnected
? "Start a conversation by typing a message below."
: "Waiting for backend connection..."}
</p>
{tools.length > 0 && (
<div className="available-tools">
<h3>Available Tools:</h3>
<ul>
{tools.map((tool) => (
<li key={tool.name}>
<strong>{tool.name}</strong> - {tool.description}
</li>
))}
</ul>
</div>
)}
</div>
)}
{messages.map((message) => (
<MessageBubble
key={message.id}
message={message}
isUser={message.type === MessageType.USER_MESSAGE}
/>
))}
<div ref={messagesEndRef} />
{isLoading && (
<div className="loading-indicator">
<div className="spinner"></div>
<span>Agent is processing...</span>
</div>
)}
</div>
{/* Error display */}
{error && (
<div className="error-message">
<span className="error-icon">⚠️</span>
{error}
<button onClick={() => setError(null)} className="close-error">
×
</button>
</div>
)}
{/* Input area */}
<form onSubmit={handleSendMessage} className="input-area">
<div className="input-wrapper">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder={isConnected ? "Type your message..." : "Waiting for connection..."}
disabled={!isConnected || isLoading}
className="message-input"
/>
<button
type="submit"
disabled={!isConnected || isLoading || !inputValue.trim()}
className="send-button"
>
{isLoading ? "⏳" : "Send"}
</button>
</div>
{/* Action buttons */}
<div className="action-buttons">
<button
type="button"
onClick={handleClearHistory}
disabled={messages.length === 0}
className="clear-button"
>
Clear History
</button>
</div>
</form>
</div>
);
};
export default ChatWindow;