feat(ui): Add import agent screen

This commit is contained in:
Richard Palethorpe
2025-04-01 22:25:21 +01:00
parent 7494aa9c26
commit 74fdfd7a55
6 changed files with 248 additions and 6 deletions

View File

@@ -1933,3 +1933,124 @@ select.form-control {
background-color: var(--light-bg); background-color: var(--light-bg);
color: var(--primary); color: var(--primary);
} }
.import-agent-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.import-agent-content {
margin-top: 20px;
}
.file-dropzone {
border: 2px dashed var(--border);
border-radius: 8px;
padding: 40px;
text-align: center;
margin-bottom: 20px;
transition: all 0.3s ease;
background: rgba(30, 30, 30, 0.7);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
}
.file-dropzone:hover {
border-color: var(--primary);
background: rgba(30, 30, 30, 0.8);
}
.dropzone-content {
color: var(--text);
}
.dropzone-content i {
font-size: 48px;
color: var(--primary);
margin-bottom: 20px;
}
.dropzone-content h2 {
margin-bottom: 10px;
color: var(--primary);
text-shadow: var(--neon-glow);
}
.dropzone-content p {
margin: 10px 0;
color: var(--text);
}
.file-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: var(--primary);
color: var(--dark-bg);
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.file-button:hover {
background: rgba(0, 255, 149, 0.8);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.file-button i {
font-size: 16px;
}
.selected-file-info {
margin-top: 20px;
padding: 20px;
background: rgba(30, 30, 30, 0.7);
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
}
.selected-file-info p {
margin-bottom: 15px;
color: var(--text);
}
.import-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: var(--primary);
color: var(--dark-bg);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 16px;
}
.import-button:hover:not(:disabled) {
background: rgba(0, 255, 149, 0.8);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.import-button:disabled {
background: rgba(0, 255, 149, 0.3);
cursor: not-allowed;
}
.import-button i {
font-size: 16px;
}
.import-button .fa-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@@ -112,9 +112,14 @@ function AgentsList() {
<div className="agents-container"> <div className="agents-container">
<header className="page-header"> <header className="page-header">
<h1>Manage Agents</h1> <h1>Manage Agents</h1>
<div className="agent-actions">
<Link to="/create" className="action-btn"> <Link to="/create" className="action-btn">
<i className="fas fa-plus"></i> Create New Agent <i className="fas fa-plus-circle"></i> Create Agent
</Link> </Link>
<Link to="/import" className="action-btn">
<i className="fas fa-upload"></i> Import Agent
</Link>
</div>
</header> </header>
{agents.length > 0 ? ( {agents.length > 0 ? (

View File

@@ -99,9 +99,18 @@ function Home() {
<Link to="/group-create" className="card-link"> <Link to="/group-create" className="card-link">
<div className="card"> <div className="card">
<h2><i className="fas fa-users"></i> Create Group</h2> <h2><i className="fas fa-users"></i> Create Group</h2>
<p>Create agent groups for collaborative intelligence.</p> <p>Create a group of agents with shared configurations and behaviors.</p>
</div> </div>
</Link> </Link>
{/* Card for Import Agent */}
<Link to="/import" className="card-link">
<div className="card">
<h2><i className="fas fa-upload"></i> Import Agent</h2>
<p>Import an existing agent configuration from a file.</p>
</div>
</Link>
</div> </div>
{stats.agents.length > 0 && ( {stats.agents.length > 0 && (

View File

@@ -0,0 +1,102 @@
import { useState } from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom';
import { agentApi } from '../utils/api';
function ImportAgent() {
const navigate = useNavigate();
const { showToast } = useOutletContext();
const [file, setFile] = useState(null);
const [loading, setLoading] = useState(false);
const handleFileChange = (e) => {
const selectedFile = e.target.files[0];
if (selectedFile) {
setFile(selectedFile);
}
};
const handleImport = async () => {
if (!file) {
showToast('Please select a file to import', 'error');
return;
}
setLoading(true);
try {
const formData = new FormData();
formData.append('file', file);
await agentApi.importAgentConfig(formData);
showToast('Agent imported successfully', 'success');
navigate('/agents');
} catch (err) {
showToast(`Error importing agent: ${err.message}`, 'error');
} finally {
setLoading(false);
}
};
return (
<div className="import-agent-container">
<header className="page-header">
<h1>
<i className="fas fa-upload"></i> Import Agent
</h1>
</header>
<div className="import-agent-content">
<div className="section-box">
<div className="file-dropzone" onDrop={(e) => {
e.preventDefault();
const droppedFile = e.dataTransfer.files[0];
if (droppedFile) {
setFile(droppedFile);
}
}}
onDragOver={(e) => e.preventDefault()}>
<div className="dropzone-content">
<i className="fas fa-cloud-upload-alt"></i>
<h2>Drop your agent file here</h2>
<p>or</p>
<label htmlFor="fileInput" className="file-button">
<i className="fas fa-folder-open"></i> Select File
</label>
<input
type="file"
id="fileInput"
accept=".json,.yaml,.yml"
onChange={handleFileChange}
style={{ display: 'none' }}
/>
</div>
</div>
{file && (
<div className="selected-file-info">
<p>Selected file: {file.name}</p>
<button
className="import-button"
onClick={handleImport}
disabled={loading}
>
{loading ? (
<>
<i className="fas fa-spinner fa-spin"></i>
Importing...
</>
) : (
<>
<i className="fas fa-upload"></i>
Import Agent
</>
)}
</button>
</div>
)}
</div>
</div>
</div>
);
}
export default ImportAgent;

View File

@@ -8,6 +8,7 @@ import Chat from './pages/Chat';
import ActionsPlayground from './pages/ActionsPlayground'; import ActionsPlayground from './pages/ActionsPlayground';
import GroupCreate from './pages/GroupCreate'; import GroupCreate from './pages/GroupCreate';
import AgentStatus from './pages/AgentStatus'; import AgentStatus from './pages/AgentStatus';
import ImportAgent from './pages/ImportAgent';
// Get the base URL from Vite's environment variables or default to '/app/' // Get the base URL from Vite's environment variables or default to '/app/'
const BASE_URL = import.meta.env.BASE_URL || '/app'; const BASE_URL = import.meta.env.BASE_URL || '/app';
@@ -46,6 +47,10 @@ export const router = createBrowserRouter([
path: 'group-create', path: 'group-create',
element: <GroupCreate /> element: <GroupCreate />
}, },
{
path: 'import',
element: <ImportAgent />
},
{ {
path: 'status/:name', path: 'status/:name',
element: <AgentStatus /> element: <AgentStatus />

View File

@@ -142,11 +142,11 @@ export const agentApi = {
}, },
// Import agent configuration // Import agent configuration
importAgentConfig: async (configData) => { importAgent: async (formData) => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.importAgent), { const response = await fetch(buildUrl(API_CONFIG.endpoints.importAgent), {
method: 'POST', method: 'POST',
headers: API_CONFIG.headers, headers: API_CONFIG.headers,
body: JSON.stringify(configData), body: formData,
}); });
return handleResponse(response); return handleResponse(response);
}, },