feat(ui): Add React based UI for the vibes at /app
This adds a completely separate frontend based on React because I found that code gen works better with React once the application gets bigger. In particular it was getting very hard to move past add connectors and actions. The idea is to replace the standard UI with this once it has been tested. But for now it is available at /app in addition to the original at / Signed-off-by: Richard Palethorpe <io@richiejp.com>
This commit is contained in:
112
webui/react-ui/src/hooks/useAgent.js
vendored
Normal file
112
webui/react-ui/src/hooks/useAgent.js
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { agentApi } from '../utils/api';
|
||||
|
||||
/**
|
||||
* Custom hook for managing agent state
|
||||
* @param {string} agentName - Name of the agent to manage
|
||||
* @returns {Object} - Agent state and management functions
|
||||
*/
|
||||
export function useAgent(agentName) {
|
||||
const [agent, setAgent] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Fetch agent configuration
|
||||
const fetchAgent = useCallback(async () => {
|
||||
if (!agentName) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const config = await agentApi.getAgentConfig(agentName);
|
||||
setAgent(config);
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to fetch agent configuration');
|
||||
console.error('Error fetching agent:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [agentName]);
|
||||
|
||||
// Update agent configuration
|
||||
const updateAgent = useCallback(async (config) => {
|
||||
if (!agentName) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await agentApi.updateAgentConfig(agentName, config);
|
||||
// Refresh agent data after update
|
||||
await fetchAgent();
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to update agent configuration');
|
||||
console.error('Error updating agent:', err);
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [agentName, fetchAgent]);
|
||||
|
||||
// Toggle agent status (pause/start)
|
||||
const toggleAgentStatus = useCallback(async (isActive) => {
|
||||
if (!agentName) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
if (isActive) {
|
||||
await agentApi.pauseAgent(agentName);
|
||||
} else {
|
||||
await agentApi.startAgent(agentName);
|
||||
}
|
||||
// Refresh agent data after status change
|
||||
await fetchAgent();
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to toggle agent status');
|
||||
console.error('Error toggling agent status:', err);
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [agentName, fetchAgent]);
|
||||
|
||||
// Delete agent
|
||||
const deleteAgent = useCallback(async () => {
|
||||
if (!agentName) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await agentApi.deleteAgent(agentName);
|
||||
setAgent(null);
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to delete agent');
|
||||
console.error('Error deleting agent:', err);
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [agentName]);
|
||||
|
||||
// Load agent data on mount or when agentName changes
|
||||
useEffect(() => {
|
||||
fetchAgent();
|
||||
}, [agentName, fetchAgent]);
|
||||
|
||||
return {
|
||||
agent,
|
||||
loading,
|
||||
error,
|
||||
fetchAgent,
|
||||
updateAgent,
|
||||
toggleAgentStatus,
|
||||
deleteAgent,
|
||||
};
|
||||
}
|
||||
78
webui/react-ui/src/hooks/useChat.js
vendored
Normal file
78
webui/react-ui/src/hooks/useChat.js
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { chatApi } from '../utils/api';
|
||||
import { useSSE } from './useSSE';
|
||||
|
||||
/**
|
||||
* Custom hook for chat functionality
|
||||
* @param {string} agentName - Name of the agent to chat with
|
||||
* @returns {Object} - Chat state and functions
|
||||
*/
|
||||
export function useChat(agentName) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [sending, setSending] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Use SSE hook to receive real-time messages
|
||||
const { data: sseData, isConnected } = useSSE(agentName);
|
||||
|
||||
// Process SSE data into messages
|
||||
useEffect(() => {
|
||||
if (sseData && sseData.length > 0) {
|
||||
// Process the latest SSE data
|
||||
const latestData = sseData[sseData.length - 1];
|
||||
|
||||
if (latestData.type === 'message') {
|
||||
setMessages(prev => [...prev, {
|
||||
id: Date.now().toString(),
|
||||
sender: 'agent',
|
||||
content: latestData.content,
|
||||
timestamp: new Date().toISOString(),
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}, [sseData]);
|
||||
|
||||
// Send a message to the agent
|
||||
const sendMessage = useCallback(async (content) => {
|
||||
if (!agentName || !content) return;
|
||||
|
||||
setSending(true);
|
||||
setError(null);
|
||||
|
||||
// Add user message to the list
|
||||
const userMessage = {
|
||||
id: Date.now().toString(),
|
||||
sender: 'user',
|
||||
content,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
|
||||
try {
|
||||
await chatApi.sendMessage(agentName, content);
|
||||
// The agent's response will come through SSE
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to send message');
|
||||
console.error('Error sending message:', err);
|
||||
return false;
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
}, [agentName]);
|
||||
|
||||
// Clear chat history
|
||||
const clearChat = useCallback(() => {
|
||||
setMessages([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
messages,
|
||||
sending,
|
||||
error,
|
||||
isConnected,
|
||||
sendMessage,
|
||||
clearChat,
|
||||
};
|
||||
}
|
||||
63
webui/react-ui/src/hooks/useSSE.js
vendored
Normal file
63
webui/react-ui/src/hooks/useSSE.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { API_CONFIG } from '../utils/config';
|
||||
|
||||
/**
|
||||
* Helper function to build a full URL
|
||||
* @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
|
||||
* @returns {Object} - SSE data and connection status
|
||||
*/
|
||||
export function useSSE(agentName) {
|
||||
const [data, setData] = useState([]);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!agentName) return;
|
||||
|
||||
// Create EventSource for SSE connection
|
||||
const eventSource = new EventSource(buildUrl(API_CONFIG.endpoints.sse(agentName)));
|
||||
|
||||
// Connection opened
|
||||
eventSource.onopen = () => {
|
||||
setIsConnected(true);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
// Handle incoming messages
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
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);
|
||||
setError('SSE connection error');
|
||||
console.error('SSE connection error:', err);
|
||||
};
|
||||
|
||||
// Clean up on unmount
|
||||
return () => {
|
||||
eventSource.close();
|
||||
setIsConnected(false);
|
||||
};
|
||||
}, [agentName]);
|
||||
|
||||
// Function to clear the data
|
||||
const clearData = () => setData([]);
|
||||
|
||||
return { data, isConnected, error, clearData };
|
||||
}
|
||||
Reference in New Issue
Block a user