diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 34d6bef..9ac086f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,18 +1,36 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Sidebar } from './components/Layout/Sidebar'; import { MonitoringLayout } from './components/Monitoring/MonitoringLayout'; import { SettingsLayout } from './components/Settings/SettingsLayout'; +import { AgentLayout } from './components/Agent/AgentLayout'; +import { ResourceLayout } from './components/Resource/ResourceLayout'; import { LeftPanel } from './components/Chat/LeftPanel'; import { ChatPanel } from './components/Chat/ChatPanel'; import { RightPanel } from './components/Chat/RightPanel'; +import { AuthPage } from './components/Auth/AuthPage'; function App() { + const [isAuthenticated, setIsAuthenticated] = useState(false); const [activeTab, setActiveTab] = useState('chats'); // For LeftPanel - const [currentView, setCurrentView] = useState('dashboard'); // 'dashboard', 'settings', or 'monitoring' + const [currentView, setCurrentView] = useState('dashboard'); // 'dashboard', 'settings', 'monitoring', 'agent', 'resource' const [settingsTab, setSettingsTab] = useState('users'); // For SettingsLayout + const [agentTab, setAgentTab] = useState('worker'); // For AgentLayout + const [resourceTab, setResourceTab] = useState('skill'); // For ResourceLayout const [selectedWorkflow, setSelectedWorkflow] = useState(null); + useEffect(() => { + // Check if token exists in localStorage on mount + const token = localStorage.getItem('token'); + if (token) { + setIsAuthenticated(true); + } + }, []); + + if (!isAuthenticated) { + return setIsAuthenticated(true)} />; + } + return (
@@ -22,6 +40,10 @@ function App() { {/* Main Content Area depending on view */} {currentView === 'monitoring' ? ( + ) : currentView === 'agent' ? ( + + ) : currentView === 'resource' ? ( + ) : currentView === 'dashboard' ? ( <> {/* 2. Left Panel - Cluster Status & Workflows/Chats */} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index ce2a137..7c13b50 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -19,4 +19,18 @@ apiClient.interceptors.request.use((config) => { return config; }); +// Interceptor to catch 401 Unauthorized errors and force login +apiClient.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + // Clear token + localStorage.removeItem('token'); + // Reload the page to force the user back to the Auth view + window.location.reload(); + } + return Promise.reject(error); + } +); + export default apiClient; diff --git a/frontend/src/components/Agent/AgentLayout.tsx b/frontend/src/components/Agent/AgentLayout.tsx new file mode 100644 index 0000000..3d351b9 --- /dev/null +++ b/frontend/src/components/Agent/AgentLayout.tsx @@ -0,0 +1,43 @@ +import { Bot, Key } from 'lucide-react'; +import { ProvidersSettings } from './ProvidersSettings'; +import { WorkerIndividualSettings } from './WorkerIndividualSettings'; + +interface AgentLayoutProps { + agentTab: string; + setAgentTab: (tab: string) => void; +} + +export function AgentLayout({ agentTab, setAgentTab }: AgentLayoutProps) { + return ( +
+ {/* Agent Inner Sidebar */} +
+
+

Agents

+
+
+ + +
+
+ + {/* Agent Main Content */} +
+ {agentTab === 'worker' && } + {agentTab === 'providers' && } +
+
+ ); +} diff --git a/frontend/src/components/Settings/ProvidersSettings.tsx b/frontend/src/components/Agent/ProvidersSettings.tsx similarity index 100% rename from frontend/src/components/Settings/ProvidersSettings.tsx rename to frontend/src/components/Agent/ProvidersSettings.tsx diff --git a/frontend/src/components/Agent/WorkerIndividualSettings.tsx b/frontend/src/components/Agent/WorkerIndividualSettings.tsx new file mode 100644 index 0000000..c394e07 --- /dev/null +++ b/frontend/src/components/Agent/WorkerIndividualSettings.tsx @@ -0,0 +1,285 @@ +import { useState, useEffect } from 'react'; +import apiClient from '../../api/client'; +import { Bot, Save } from 'lucide-react'; +import type { Provider } from '../../types'; + +function WorkerIndividualForm({ providers }: { providers: Provider[] }) { + const [formData, setFormData] = useState({ + agent_name: '', + agent_type: 'OrdinaryIndividual', + description: '', + provider_title: providers.length > 0 ? providers[0].provider_title : '', + model_id: '', + system_prompt: '', + output_template: '{}', + bound_skill: '{}', + workspace: '[]' + }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + + // Update initial provider_title when providers load + useEffect(() => { + if (providers.length > 0 && !formData.provider_title) { + setFormData(prev => ({ ...prev, provider_title: providers[0].provider_title })); + } + }, [providers, formData.provider_title]); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(''); + try { + const payload = { + ...formData, + output_template: JSON.parse(formData.output_template), + bound_skill: JSON.parse(formData.bound_skill), + workspace: JSON.parse(formData.workspace) + }; + await apiClient.post('/api/v1/agent/worker', payload); + setMessage('Successfully created worker individual'); + setFormData({ + agent_name: '', + agent_type: 'OrdinaryIndividual', + description: '', + provider_title: '', + model_id: '', + system_prompt: '', + output_template: '{}', + bound_skill: '{}', + workspace: '[]' + }); + } catch (err: any) { + console.error(err); + setMessage(err.response?.data?.detail || 'Failed to create worker individual. Ensure JSON fields are valid.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +