feat(agents): own agents page with tree sidebar in StartLayout, like settings/help
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { NavLink, Outlet } from 'react-router-dom';
|
||||||
|
import { useUIStore } from '@/store/uiStore';
|
||||||
|
import { ChevronRight, ChevronDown, Bot, Zap, Brain, Cpu, Settings2, Activity, MessageSquare } from 'lucide-react';
|
||||||
|
|
||||||
|
interface AgentNode {
|
||||||
|
title: string;
|
||||||
|
path?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
children?: AgentNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AGENT_TREE: AgentNode[] = [
|
||||||
|
{
|
||||||
|
title: 'Agenten',
|
||||||
|
icon: <Bot className="w-4 h-4" />,
|
||||||
|
children: [
|
||||||
|
{ title: 'Übersicht', path: '/agents/overview' },
|
||||||
|
{ title: 'Ausführungen', path: '/agents/runs' },
|
||||||
|
{ title: 'Versionen', path: '/agents/versions' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Automation',
|
||||||
|
icon: <Zap className="w-4 h-4" />,
|
||||||
|
children: [
|
||||||
|
{ title: 'Übersicht', path: '/agents/automation' },
|
||||||
|
{ title: 'Einstellungen', path: '/agents/automation-settings' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'KI',
|
||||||
|
icon: <Brain className="w-4 h-4" />,
|
||||||
|
children: [
|
||||||
|
{ title: 'Assistent', path: '/agents/ai-assistant' },
|
||||||
|
{ title: 'Einstellungen', path: '/agents/ai-settings' },
|
||||||
|
{ title: 'Proactive AI', path: '/agents/ai-proactive' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function TreeItem({ node, depth }: { node: AgentNode; depth: number }) {
|
||||||
|
const [expanded, setExpanded] = useState(depth < 1);
|
||||||
|
const hasChildren = node.children && node.children.length > 0;
|
||||||
|
const paddingLeft = depth * 16 + 12;
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-secondary-700 hover:bg-secondary-100 transition-colors min-h-touch"
|
||||||
|
style={{ paddingLeft }}
|
||||||
|
aria-expanded={expanded}
|
||||||
|
>
|
||||||
|
{expanded ? <ChevronDown className="w-3.5 h-3.5 flex-shrink-0" /> : <ChevronRight className="w-3.5 h-3.5 flex-shrink-0" />}
|
||||||
|
{node.icon}
|
||||||
|
<span>{node.title}</span>
|
||||||
|
</button>
|
||||||
|
{expanded && (
|
||||||
|
<div>
|
||||||
|
{node.children!.map((child) => (
|
||||||
|
<TreeItem key={child.path || child.title} node={child} depth={depth + 1} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={node.path!}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors min-h-touch ${
|
||||||
|
isActive
|
||||||
|
? 'bg-primary-100 text-primary-700 font-medium'
|
||||||
|
: 'text-secondary-600 hover:bg-secondary-100 hover:text-secondary-900'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
style={{ paddingLeft: paddingLeft + 20 }}
|
||||||
|
>
|
||||||
|
{node.icon}
|
||||||
|
<span>{node.title}</span>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AgentsPage() {
|
||||||
|
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full overflow-hidden" data-testid="agents-page">
|
||||||
|
<aside className={`${sidebarOpen ? 'w-64 border-r border-secondary-200' : 'w-0'} flex-shrink-0 min-h-[calc(100vh-4rem)] transition-all duration-200 overflow-hidden`}>
|
||||||
|
<div className="w-64 flex-shrink-0 p-4">
|
||||||
|
<h1 className="text-xl font-bold text-secondary-900 mb-4">Agenten</h1>
|
||||||
|
<nav className="space-y-0.5 overflow-y-auto" role="navigation" aria-label="Agenten Navigation">
|
||||||
|
{AGENT_TREE.map((node) => (
|
||||||
|
<TreeItem key={node.title} node={node} depth={0} />
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<main className="flex-1 p-6 overflow-y-auto" data-testid="agents-content">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ export function StartPage() {
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ id: 'workspaces', label: 'Workspaces', icon: <LayoutGrid className="w-4 h-4" />, active: true },
|
{ id: 'workspaces', label: 'Workspaces', icon: <LayoutGrid className="w-4 h-4" />, active: true },
|
||||||
{ id: 'settings', label: 'Einstellungen', icon: <Settings className="w-4 h-4" />, onClick: () => navigate('/settings') },
|
{ id: 'settings', label: 'Einstellungen', icon: <Settings className="w-4 h-4" />, onClick: () => navigate('/settings') },
|
||||||
{ id: 'agents', label: 'Agenten', icon: <Bot className="w-4 h-4" />, onClick: () => navigate('/agents') },
|
{ id: 'agents', label: 'Agenten', icon: <Bot className="w-4 h-4" />, onClick: () => navigate('/agents/overview') },
|
||||||
{ id: 'help', label: 'Hilfe', icon: <HelpCircle className="w-4 h-4" />, onClick: () => navigate('/help/welcome') },
|
{ id: 'help', label: 'Hilfe', icon: <HelpCircle className="w-4 h-4" />, onClick: () => navigate('/help/welcome') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function AgentsOverviewPage() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-2xl font-bold text-secondary-900 mb-4">Agenten Übersicht</h1>
|
||||||
|
<p className="text-secondary-600 mb-6">
|
||||||
|
Verwalten Sie KI-Agenten, führen Sie diese aus und überwachen Sie deren Ausführungen.
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div className="p-6 bg-white rounded-xl border border-secondary-200">
|
||||||
|
<div className="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center mb-3">
|
||||||
|
<svg className="w-6 h-6 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M3 13l2-6h14l2 6M3 13v4a2 2 0 002 2h14a2 2 0 002-2v-4" /></svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-secondary-900">Agenten</h3>
|
||||||
|
<p className="text-sm text-secondary-500 mt-1">KI-Agenten erstellen, konfigurieren und ausführen</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 bg-white rounded-xl border border-secondary-200">
|
||||||
|
<div className="w-12 h-12 bg-warning-100 rounded-lg flex items-center justify-center mb-3">
|
||||||
|
<svg className="w-6 h-6 text-warning-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-secondary-900">Automation</h3>
|
||||||
|
<p className="text-sm text-secondary-500 mt-1">Automatisierte Workflows und Trigger</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 bg-white rounded-xl border border-secondary-200">
|
||||||
|
<div className="w-12 h-12 bg-success-100 rounded-lg flex items-center justify-center mb-3">
|
||||||
|
<svg className="w-6 h-6 text-success-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /></svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-secondary-900">KI</h3>
|
||||||
|
<p className="text-sm text-secondary-500 mt-1">KI-Assistent und Proactive AI</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function AgentsPlaceholderPage() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-2xl font-bold text-secondary-900 mb-4">Agenten</h1>
|
||||||
|
<p className="text-secondary-600">
|
||||||
|
Diese Seite wird gerade erstellt. Wählen Sie ein Thema aus dem Menü links.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -77,6 +77,9 @@ const HelpNavigationPage = React.lazy(() => import('@/pages/help/HelpNavigation'
|
|||||||
const HelpContactsPage = React.lazy(() => import('@/pages/help/HelpContacts').then(m => ({ default: m.HelpContactsPage })));
|
const HelpContactsPage = React.lazy(() => import('@/pages/help/HelpContacts').then(m => ({ default: m.HelpContactsPage })));
|
||||||
const HelpMailSetupPage = React.lazy(() => import('@/pages/help/HelpMailSetup').then(m => ({ default: m.HelpMailSetupPage })));
|
const HelpMailSetupPage = React.lazy(() => import('@/pages/help/HelpMailSetup').then(m => ({ default: m.HelpMailSetupPage })));
|
||||||
const HelpPlaceholderPage = React.lazy(() => import('@/pages/help/HelpPlaceholder').then(m => ({ default: m.HelpPlaceholderPage })));
|
const HelpPlaceholderPage = React.lazy(() => import('@/pages/help/HelpPlaceholder').then(m => ({ default: m.HelpPlaceholderPage })));
|
||||||
|
const AgentsPage = React.lazy(() => import('@/pages/Agents').then(m => ({ default: m.AgentsPage })));
|
||||||
|
const AgentsOverviewPage = React.lazy(() => import('@/pages/agents/AgentsOverview').then(m => ({ default: m.AgentsOverviewPage })));
|
||||||
|
const AgentsPlaceholderPage = React.lazy(() => import('@/pages/agents/AgentsPlaceholder').then(m => ({ default: m.AgentsPlaceholderPage })));
|
||||||
const ApiDocsPage = React.lazy(() => import('@/pages/ApiDocs').then(m => ({ default: m.ApiDocsPage })));
|
const ApiDocsPage = React.lazy(() => import('@/pages/ApiDocs').then(m => ({ default: m.ApiDocsPage })));
|
||||||
|
|
||||||
/** Centered spinner fallback for lazy-loaded routes */
|
/** Centered spinner fallback for lazy-loaded routes */
|
||||||
@@ -158,6 +161,19 @@ const router = createBrowserRouter([
|
|||||||
{ path: '*', element: withSuspense(<HelpPlaceholderPage />) },
|
{ path: '*', element: withSuspense(<HelpPlaceholderPage />) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/agents',
|
||||||
|
element: withSuspense(<AgentsPage />),
|
||||||
|
children: [
|
||||||
|
{ path: 'overview', element: withSuspense(<AgentsOverviewPage />) },
|
||||||
|
{ path: 'automation', element: withSuspense(<AutomationDashboardPage />) },
|
||||||
|
{ path: 'automation-settings', element: withSuspense(<AutomationSettingsPage />) },
|
||||||
|
{ path: 'ai-assistant', element: withSuspense(<AIAssistantPage />) },
|
||||||
|
{ path: 'ai-settings', element: withSuspense(<AISettingsPage />) },
|
||||||
|
{ path: 'ai-proactive', element: withSuspense(<ProactiveAISettings />) },
|
||||||
|
{ path: '*', element: withSuspense(<AgentsPlaceholderPage />) },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
element: <PermissionRoute permission="settings:read">{withSuspense(<SettingsPage />)}</PermissionRoute>,
|
element: <PermissionRoute permission="settings:read">{withSuspense(<SettingsPage />)}</PermissionRoute>,
|
||||||
@@ -212,8 +228,6 @@ const router = createBrowserRouter([
|
|||||||
{ path: '/mail', element: <PermissionRoute permission="mail:read">{withSuspense(<MailPage />)}</PermissionRoute> },
|
{ path: '/mail', element: <PermissionRoute permission="mail:read">{withSuspense(<MailPage />)}</PermissionRoute> },
|
||||||
{ path: '/mail/settings', element: <PermissionRoute permission="mail:read">{withSuspense(<MailSettingsPage />)}</PermissionRoute> },
|
{ path: '/mail/settings', element: <PermissionRoute permission="mail:read">{withSuspense(<MailSettingsPage />)}</PermissionRoute> },
|
||||||
{ path: '/ai-assistant', element: <PermissionRoute permission="ai:read">{withSuspense(<AIAssistantPage />)}</PermissionRoute> },
|
{ path: '/ai-assistant', element: <PermissionRoute permission="ai:read">{withSuspense(<AIAssistantPage />)}</PermissionRoute> },
|
||||||
{ path: '/automation', element: <PermissionRoute permission="automation:read">{withSuspense(<AutomationDashboardPage />)}</PermissionRoute> },
|
|
||||||
{ path: '/agents', element: <PermissionRoute permission="automation:read">{withSuspense(<AgentDashboardPage />)}</PermissionRoute> },
|
|
||||||
{ path: '/reports', element: <PermissionRoute permission="reports:read">{withSuspense(<ReportsPage />)}</PermissionRoute> },
|
{ path: '/reports', element: <PermissionRoute permission="reports:read">{withSuspense(<ReportsPage />)}</PermissionRoute> },
|
||||||
{ path: '/tasks', element: <PermissionRoute permission="tasks:read">{withSuspense(<TasksPage />)}</PermissionRoute> },
|
{ path: '/tasks', element: <PermissionRoute permission="tasks:read">{withSuspense(<TasksPage />)}</PermissionRoute> },
|
||||||
{ path: '/communication', element: <PermissionRoute permission="communication:read">{withSuspense(<CommunicationPage />)}</PermissionRoute> },
|
{ path: '/communication', element: <PermissionRoute permission="communication:read">{withSuspense(<CommunicationPage />)}</PermissionRoute> },
|
||||||
|
|||||||
Reference in New Issue
Block a user