fix(ui): Re-add Chat
This commit is contained in:
122
webui/app.go
122
webui/app.go
@@ -301,6 +301,128 @@ func (a *App) Chat(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChatAPI provides a JSON-based API for chat functionality
|
||||||
|
// This is designed to work better with the React UI
|
||||||
|
func (a *App) ChatAPI(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Parse the request body
|
||||||
|
payload := struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(map[string]interface{}{
|
||||||
|
"error": "Invalid request format",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get agent name from URL parameter
|
||||||
|
agentName := c.Params("name")
|
||||||
|
|
||||||
|
// Validate message
|
||||||
|
message := strings.TrimSpace(payload.Message)
|
||||||
|
if message == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(map[string]interface{}{
|
||||||
|
"error": "Message cannot be empty",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the agent from the pool
|
||||||
|
agent := pool.GetAgent(agentName)
|
||||||
|
if agent == nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(map[string]interface{}{
|
||||||
|
"error": "Agent not found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the SSE manager for this agent
|
||||||
|
manager := pool.GetManager(agentName)
|
||||||
|
|
||||||
|
// Create a unique message ID
|
||||||
|
messageID := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Send user message event via SSE
|
||||||
|
userMessageData, err := json.Marshal(map[string]interface{}{
|
||||||
|
"id": messageID + "-user",
|
||||||
|
"sender": "user",
|
||||||
|
"content": message,
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
xlog.Error("Error marshaling user message", "error", err)
|
||||||
|
} else {
|
||||||
|
manager.Send(
|
||||||
|
sse.NewMessage(string(userMessageData)).WithEvent("json_message"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send processing status
|
||||||
|
statusData, err := json.Marshal(map[string]interface{}{
|
||||||
|
"status": "processing",
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
xlog.Error("Error marshaling status message", "error", err)
|
||||||
|
} else {
|
||||||
|
manager.Send(
|
||||||
|
sse.NewMessage(string(statusData)).WithEvent("status"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the message asynchronously
|
||||||
|
go func() {
|
||||||
|
// Ask the agent for a response
|
||||||
|
response := agent.Ask(coreTypes.WithText(message))
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
// Send error message
|
||||||
|
xlog.Error("Error asking agent", "agent", agentName, "error", response.Error)
|
||||||
|
errorData, err := json.Marshal(map[string]interface{}{
|
||||||
|
"error": response.Error.Error(),
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
xlog.Error("Error marshaling error message", "error", err)
|
||||||
|
} else {
|
||||||
|
manager.Send(
|
||||||
|
sse.NewMessage(string(errorData)).WithEvent("error"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send agent response
|
||||||
|
xlog.Info("Response from agent", "agent", agentName, "response", response.Response)
|
||||||
|
responseData, err := json.Marshal(map[string]interface{}{
|
||||||
|
"id": messageID + "-agent",
|
||||||
|
"sender": "agent",
|
||||||
|
"content": response.Response,
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
xlog.Error("Error marshaling agent response", "error", err)
|
||||||
|
} else {
|
||||||
|
manager.Send(
|
||||||
|
sse.NewMessage(string(responseData)).WithEvent("json_message"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send completed status
|
||||||
|
completedData, err := json.Marshal(map[string]interface{}{
|
||||||
|
"status": "completed",
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
xlog.Error("Error marshaling completed status", "error", err)
|
||||||
|
} else {
|
||||||
|
manager.Send(
|
||||||
|
sse.NewMessage(string(completedData)).WithEvent("status"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Return immediate success response
|
||||||
|
return c.Status(fiber.StatusAccepted).JSON(map[string]interface{}{
|
||||||
|
"status": "message_received",
|
||||||
|
"message_id": messageID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
payload := struct {
|
payload := struct {
|
||||||
|
|||||||
102
webui/react-ui/src/hooks/useChat.js
vendored
102
webui/react-ui/src/hooks/useChat.js
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||||
import { chatApi } from '../utils/api';
|
import { chatApi } from '../utils/api';
|
||||||
import { useSSE } from './useSSE';
|
import { useSSE } from './useSSE';
|
||||||
|
|
||||||
@@ -11,37 +11,103 @@ export function useChat(agentName) {
|
|||||||
const [messages, setMessages] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const processedMessageIds = useRef(new Set());
|
||||||
|
|
||||||
// Use SSE hook to receive real-time messages
|
// Use SSE hook to receive real-time messages
|
||||||
const { data: sseData, isConnected } = useSSE(agentName);
|
const { messages: sseMessages, statusUpdates, errorMessages, isConnected } = useSSE(agentName);
|
||||||
|
|
||||||
// Process SSE data into messages
|
// Process SSE messages into chat messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sseData && sseData.length > 0) {
|
if (!sseMessages || sseMessages.length === 0) return;
|
||||||
// Process the latest SSE data
|
|
||||||
const latestData = sseData[sseData.length - 1];
|
|
||||||
|
|
||||||
if (latestData.type === 'message') {
|
// Process the latest SSE message
|
||||||
|
const latestMessage = sseMessages[sseMessages.length - 1];
|
||||||
|
|
||||||
|
// Skip if we've already processed this message
|
||||||
|
if (processedMessageIds.current.has(latestMessage.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle JSON messages
|
||||||
|
if (latestMessage.type === 'json_message') {
|
||||||
|
try {
|
||||||
|
// The message should already be a parsed JSON object
|
||||||
|
const messageData = latestMessage.content;
|
||||||
|
|
||||||
|
// Skip if we've already processed this message ID
|
||||||
|
if (processedMessageIds.current.has(messageData.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to processed set to avoid duplicates
|
||||||
|
processedMessageIds.current.add(messageData.id);
|
||||||
|
|
||||||
|
// Add the message to our state
|
||||||
setMessages(prev => [...prev, {
|
setMessages(prev => [...prev, {
|
||||||
id: Date.now().toString(),
|
id: messageData.id,
|
||||||
sender: 'agent',
|
sender: messageData.sender,
|
||||||
content: latestData.content,
|
content: messageData.content,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: messageData.timestamp,
|
||||||
}]);
|
}]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error processing JSON message:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [sseData]);
|
}, [sseMessages]);
|
||||||
|
|
||||||
|
// Process status updates
|
||||||
|
useEffect(() => {
|
||||||
|
if (!statusUpdates || statusUpdates.length === 0) return;
|
||||||
|
|
||||||
|
const latestStatus = statusUpdates[statusUpdates.length - 1];
|
||||||
|
|
||||||
|
// Handle status updates
|
||||||
|
if (latestStatus.type === 'status') {
|
||||||
|
try {
|
||||||
|
// The status should be a parsed JSON object
|
||||||
|
const statusData = latestStatus.content;
|
||||||
|
|
||||||
|
if (statusData.status === 'processing') {
|
||||||
|
setSending(true);
|
||||||
|
} else if (statusData.status === 'completed') {
|
||||||
|
setSending(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error processing status update:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [statusUpdates]);
|
||||||
|
|
||||||
|
// Process error messages
|
||||||
|
useEffect(() => {
|
||||||
|
if (!errorMessages || errorMessages.length === 0) return;
|
||||||
|
|
||||||
|
const latestError = errorMessages[errorMessages.length - 1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// The error should be a parsed JSON object
|
||||||
|
const errorData = latestError.content;
|
||||||
|
|
||||||
|
if (errorData.error) {
|
||||||
|
setError(errorData.error);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error processing error message:', err);
|
||||||
|
}
|
||||||
|
}, [errorMessages]);
|
||||||
|
|
||||||
// Send a message to the agent
|
// Send a message to the agent
|
||||||
const sendMessage = useCallback(async (content) => {
|
const sendMessage = useCallback(async (content) => {
|
||||||
if (!agentName || !content) return;
|
if (!agentName || !content) return false;
|
||||||
|
|
||||||
setSending(true);
|
setSending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Add user message to the list
|
// Add user message to the local state immediately for better UX
|
||||||
|
const messageId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
const userMessage = {
|
const userMessage = {
|
||||||
id: Date.now().toString(),
|
id: messageId,
|
||||||
sender: 'user',
|
sender: 'user',
|
||||||
content,
|
content,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -50,21 +116,21 @@ export function useChat(agentName) {
|
|||||||
setMessages(prev => [...prev, userMessage]);
|
setMessages(prev => [...prev, userMessage]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Use the JSON-based API endpoint
|
||||||
await chatApi.sendMessage(agentName, content);
|
await chatApi.sendMessage(agentName, content);
|
||||||
// The agent's response will come through SSE
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || 'Failed to send message');
|
setError(err.message || 'Failed to send message');
|
||||||
console.error('Error sending message:', err);
|
console.error('Error sending message:', err);
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
setSending(false);
|
setSending(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}, [agentName]);
|
}, [agentName]);
|
||||||
|
|
||||||
// Clear chat history
|
// Clear chat history
|
||||||
const clearChat = useCallback(() => {
|
const clearChat = useCallback(() => {
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
|
processedMessageIds.current.clear();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
143
webui/react-ui/src/hooks/useSSE.js
vendored
143
webui/react-ui/src/hooks/useSSE.js
vendored
@@ -1,63 +1,130 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { API_CONFIG } from '../utils/config';
|
import { API_CONFIG } from '../utils/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to build a full URL
|
* Custom hook for Server-Sent Events (SSE)
|
||||||
* @param {string} endpoint - API endpoint
|
|
||||||
* @returns {string} - Full URL
|
|
||||||
*/
|
|
||||||
const buildUrl = (endpoint) => {
|
|
||||||
return `${API_CONFIG.baseUrl}${endpoint.startsWith('/') ? endpoint.substring(1) : endpoint}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook for handling Server-Sent Events (SSE)
|
|
||||||
* @param {string} agentName - Name of the agent to connect to
|
* @param {string} agentName - Name of the agent to connect to
|
||||||
* @returns {Object} - SSE data and connection status
|
* @returns {Object} - SSE state and messages
|
||||||
*/
|
*/
|
||||||
export function useSSE(agentName) {
|
export function useSSE(agentName) {
|
||||||
const [data, setData] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
|
const [statusUpdates, setStatusUpdates] = useState([]);
|
||||||
|
const [errorMessages, setErrorMessages] = useState([]);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const eventSourceRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
// Connect to SSE endpoint
|
||||||
|
const connect = useCallback(() => {
|
||||||
if (!agentName) return;
|
if (!agentName) return;
|
||||||
|
|
||||||
// Create EventSource for SSE connection
|
// Close existing connection if any
|
||||||
const eventSource = new EventSource(buildUrl(API_CONFIG.endpoints.sse(agentName)));
|
if (eventSourceRef.current) {
|
||||||
|
eventSourceRef.current.close();
|
||||||
|
}
|
||||||
|
|
||||||
// Connection opened
|
// Create a new EventSource connection
|
||||||
|
const sseUrl = `${API_CONFIG.baseUrl}${API_CONFIG.endpoints.sse(agentName)}`;
|
||||||
|
const eventSource = new EventSource(sseUrl);
|
||||||
|
eventSourceRef.current = eventSource;
|
||||||
|
|
||||||
|
// Handle connection open
|
||||||
eventSource.onopen = () => {
|
eventSource.onopen = () => {
|
||||||
|
console.log('SSE connection opened');
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
setError(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle incoming messages
|
// Handle connection error
|
||||||
eventSource.onmessage = (event) => {
|
eventSource.onerror = (error) => {
|
||||||
try {
|
console.error('SSE connection error:', error);
|
||||||
const parsedData = JSON.parse(event.data);
|
|
||||||
setData((prevData) => [...prevData, parsedData]);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error parsing SSE data:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
eventSource.onerror = (err) => {
|
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
setError('SSE connection error');
|
|
||||||
console.error('SSE connection error:', err);
|
// Try to reconnect after a delay
|
||||||
|
setTimeout(() => {
|
||||||
|
if (eventSourceRef.current === eventSource) {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clean up on unmount
|
// Handle 'json_message' event
|
||||||
|
eventSource.addEventListener('json_message', (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const timestamp = data.timestamp || new Date().toISOString();
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
id: `json-message-${Date.now()}`,
|
||||||
|
type: 'json_message',
|
||||||
|
content: data,
|
||||||
|
timestamp,
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing JSON message:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle 'status' event
|
||||||
|
eventSource.addEventListener('status', (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const timestamp = data.timestamp || new Date().toISOString();
|
||||||
|
|
||||||
|
setStatusUpdates(prev => [...prev, {
|
||||||
|
id: `json-status-${Date.now()}`,
|
||||||
|
type: 'status',
|
||||||
|
content: data,
|
||||||
|
timestamp,
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing status message:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle 'error' event
|
||||||
|
eventSource.addEventListener('error', (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const timestamp = data.timestamp || new Date().toISOString();
|
||||||
|
|
||||||
|
setErrorMessages(prev => [...prev, {
|
||||||
|
id: `error-${Date.now()}`,
|
||||||
|
type: 'error',
|
||||||
|
content: data,
|
||||||
|
timestamp,
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing error message:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
setIsConnected(false);
|
|
||||||
};
|
};
|
||||||
}, [agentName]);
|
}, [agentName]);
|
||||||
|
|
||||||
// Function to clear the data
|
// Connect on mount and when agentName changes
|
||||||
const clearData = () => setData([]);
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
|
||||||
return { data, isConnected, error, clearData };
|
// Cleanup on unmount
|
||||||
|
return () => {
|
||||||
|
if (eventSourceRef.current) {
|
||||||
|
eventSourceRef.current.close();
|
||||||
|
eventSourceRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [connect]);
|
||||||
|
|
||||||
|
// Reconnect function
|
||||||
|
const reconnect = useCallback(() => {
|
||||||
|
connect();
|
||||||
|
}, [connect]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
statusUpdates,
|
||||||
|
errorMessages,
|
||||||
|
isConnected,
|
||||||
|
reconnect,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,64 +41,94 @@ function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle pressing Enter to send (Shift+Enter for new line)
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-container">
|
<div className="agents-container">
|
||||||
<header className="chat-header">
|
<header className="page-header">
|
||||||
<h1>Chat with {name}</h1>
|
<h1>Chat with {name}</h1>
|
||||||
<div className="connection-status">
|
<div className="connection-status" style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<span className={`status-indicator ${isConnected ? 'connected' : 'disconnected'}`}>
|
<span
|
||||||
|
className={isConnected ? 'active' : 'inactive'}
|
||||||
|
style={{
|
||||||
|
position: 'static',
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '5px 12px',
|
||||||
|
borderRadius: '20px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
fontWeight: '500',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
boxShadow: '0 0 10px rgba(0, 0, 0, 0.2)',
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{isConnected ? 'Connected' : 'Disconnected'}
|
{isConnected ? 'Connected' : 'Disconnected'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="clear-chat-btn"
|
className="action-btn delete-btn"
|
||||||
onClick={clearChat}
|
onClick={clearChat}
|
||||||
disabled={messages.length === 0}
|
disabled={messages.length === 0}
|
||||||
>
|
>
|
||||||
Clear Chat
|
<i className="fas fa-trash-alt"></i> Clear Chat
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="messages-container">
|
<div className="chat-container">
|
||||||
{messages.length === 0 ? (
|
<div className="chat-messages">
|
||||||
<div className="empty-chat">
|
{messages.length === 0 ? (
|
||||||
<p>No messages yet. Start a conversation with {name}!</p>
|
<div className="no-agents">
|
||||||
</div>
|
<h2>No messages yet</h2>
|
||||||
) : (
|
<p>Start a conversation with {name}!</p>
|
||||||
messages.map((msg) => (
|
|
||||||
<div
|
|
||||||
key={msg.id}
|
|
||||||
className={`message ${msg.sender === 'user' ? 'user-message' : 'agent-message'}`}
|
|
||||||
>
|
|
||||||
<div className="message-content">
|
|
||||||
{msg.content}
|
|
||||||
</div>
|
|
||||||
<div className="message-timestamp">
|
|
||||||
{new Date(msg.timestamp).toLocaleTimeString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))
|
) : (
|
||||||
)}
|
messages.map((msg) => (
|
||||||
<div ref={messagesEndRef} />
|
<div
|
||||||
</div>
|
key={msg.id}
|
||||||
|
className={`message ${msg.sender === 'user' ? 'message-user' : 'message-agent'}`}
|
||||||
|
>
|
||||||
|
<div className="message-content">
|
||||||
|
{msg.content}
|
||||||
|
</div>
|
||||||
|
<div className="message-timestamp">
|
||||||
|
{new Date(msg.timestamp).toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<form className="message-form" onSubmit={handleSubmit}>
|
<div className="chat-input">
|
||||||
<input
|
<form className="message-form" onSubmit={handleSubmit} style={{ display: 'flex', gap: '1rem', width: '100%' }}>
|
||||||
type="text"
|
<textarea
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="Type your message..."
|
onKeyDown={handleKeyDown}
|
||||||
disabled={sending || !isConnected}
|
placeholder="Type your message... (Press Enter to send, Shift+Enter for new line)"
|
||||||
className="message-input"
|
disabled={sending || !isConnected}
|
||||||
/>
|
className="form-control"
|
||||||
<button
|
rows={2}
|
||||||
type="submit"
|
style={{ flex: 1, resize: 'vertical', minHeight: '38px', maxHeight: '150px' }}
|
||||||
disabled={sending || !message.trim() || !isConnected}
|
/>
|
||||||
className="send-button"
|
<button
|
||||||
>
|
type="submit"
|
||||||
{sending ? 'Sending...' : 'Send'}
|
disabled={sending || !message.trim() || !isConnected}
|
||||||
</button>
|
className="action-btn chat-btn"
|
||||||
</form>
|
style={{ alignSelf: 'flex-end' }}
|
||||||
|
>
|
||||||
|
<i className={`fas ${sending ? 'fa-spinner fa-spin' : 'fa-paper-plane'}`}></i> {sending ? 'Sending...' : 'Send'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
4
webui/react-ui/src/utils/api.js
vendored
4
webui/react-ui/src/utils/api.js
vendored
@@ -177,9 +177,9 @@ export const agentApi = {
|
|||||||
|
|
||||||
// Chat-related API calls
|
// Chat-related API calls
|
||||||
export const chatApi = {
|
export const chatApi = {
|
||||||
// Send a chat message to an agent
|
// Send a message to an agent using the JSON-based API
|
||||||
sendMessage: async (name, message) => {
|
sendMessage: async (name, message) => {
|
||||||
const response = await fetch(buildUrl(API_CONFIG.endpoints.chat(name)), {
|
const response = await fetch(buildUrl(API_CONFIG.endpoints.chatApi(name)), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: API_CONFIG.headers,
|
headers: API_CONFIG.headers,
|
||||||
body: JSON.stringify({ message }),
|
body: JSON.stringify({ message }),
|
||||||
|
|||||||
1
webui/react-ui/src/utils/config.js
vendored
1
webui/react-ui/src/utils/config.js
vendored
@@ -34,6 +34,7 @@ export const API_CONFIG = {
|
|||||||
|
|
||||||
// Chat endpoints
|
// Chat endpoints
|
||||||
chat: (name) => `/chat/${name}`,
|
chat: (name) => `/chat/${name}`,
|
||||||
|
chatApi: (name) => `/api/chat/${name}`,
|
||||||
notify: (name) => `/notify/${name}`,
|
notify: (name) => `/notify/${name}`,
|
||||||
responses: '/v1/responses',
|
responses: '/v1/responses',
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
|||||||
webapp.Put("/api/agent/:name/pause", app.Pause(pool))
|
webapp.Put("/api/agent/:name/pause", app.Pause(pool))
|
||||||
webapp.Put("/api/agent/:name/start", app.Start(pool))
|
webapp.Put("/api/agent/:name/start", app.Start(pool))
|
||||||
|
|
||||||
|
// Add JSON-based chat API endpoint
|
||||||
|
webapp.Post("/api/chat/:name", app.ChatAPI(pool))
|
||||||
|
|
||||||
webapp.Post("/v1/responses", app.Responses(pool))
|
webapp.Post("/v1/responses", app.Responses(pool))
|
||||||
|
|
||||||
webapp.Get("/talk/:name", func(c *fiber.Ctx) error {
|
webapp.Get("/talk/:name", func(c *fiber.Ctx) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user