Files
boc/web-v2/src/pages/AgentLayerPage.tsx
T
Bernt 0b416b0f13
BOC CI/CD / Test (push) Failing after 2s
BOC CI/CD / Security Scan (push) Has been skipped
BOC CI/CD / Build & Push (push) Has been skipped
BOC CI/CD / Deploy to Staging (push) Has been skipped
BOC CI/CD / Deploy to Production (push) Has been skipped
feat(agent): activate BOC agent layer
- Add agent orchestrator API (/api/v1/agents/*)
- Connect frontend AgentContext to real backend
- Add AgentLayerPage with full agent management
- Implement specialist agents: finance, sales, hr, crm, legal, marketing, dashboard
- Add authentication, RBAC, tenant isolation on agent endpoints
- Add rate limiting and audit logging
- Update sidebar with Agent Layer navigation
- Build fresh web-v2 dist
2026-08-12 07:50:12 +00:00

158 lines
5.1 KiB
TypeScript

import { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import {
Bot,
Users,
TrendingUp,
Megaphone,
Share2,
Wallet,
Calculator,
Briefcase,
FileText,
ShieldCheck,
HeadphonesIcon,
FolderKanban,
Zap,
Sparkles,
Activity,
CheckCircle2,
AlertCircle,
XCircle
} from 'lucide-react'
import { getAllAgents, AgentContext } from '@/components/agent/AgentContext'
import { AgentChat } from '@/components/agent/AgentChat'
import { cn } from '@/lib/utils'
const agentIcons: Record<string, React.ElementType> = {
crm: Users,
sales: TrendingUp,
marketing: Megaphone,
social: Share2,
finance: Wallet,
accounting: Calculator,
hr: Briefcase,
legal: FileText,
compliance: ShieldCheck,
support: HeadphonesIcon,
projects: FolderKanban,
automation: Zap,
dashboard: Sparkles
};
const statusConfig = {
operational: { icon: CheckCircle2, color: 'text-green-500', bg: 'bg-green-500/10', label: 'Operational' },
degraded: { icon: AlertCircle, color: 'text-yellow-500', bg: 'bg-yellow-500/10', label: 'Degraded' },
error: { icon: XCircle, color: 'text-red-500', bg: 'bg-red-500/10', label: 'Error' },
disabled: { icon: XCircle, color: 'text-gray-400', bg: 'bg-gray-400/10', label: 'Disabled' }
};
export function AgentLayerPage() {
const [selectedAgent, setSelectedAgent] = useState<string | null>(null);
const [agents, setAgents] = useState<AgentContext[]>([]);
const [chatOpen, setChatOpen] = useState(false);
useEffect(() => {
setAgents(getAllAgents());
}, []);
const handleAgentClick = (rum: string) => {
setSelectedAgent(rum);
setChatOpen(true);
};
return (
<div className="space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-text-primary">BOC Agent Layer</h1>
<p className="text-text-secondary mt-1">
Professionellt agentlager för hela organisationen
</p>
</div>
<div className="flex items-center gap-2 px-4 py-2 bg-green-500/10 rounded-lg">
<Activity size={16} className="text-green-500" />
<span className="text-sm font-medium text-green-600">
{agents.filter(a => a.status === 'operational').length} / {agents.length} Operational
</span>
</div>
</div>
{/* Agent Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{agents.map((agent, index) => {
const Icon = agentIcons[agent.rum] || Bot;
const status = statusConfig[agent.status];
const StatusIcon = status.icon;
return (
<motion.div
key={agent.rum}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
onClick={() => handleAgentClick(agent.rum)}
className={cn(
'group relative p-5 rounded-2xl border border-border bg-surface',
'hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5',
'cursor-pointer transition-all duration-200'
)}
>
{/* Status Indicator */}
<div className="absolute top-4 right-4">
<div className={cn('flex items-center gap-1.5 px-2 py-1 rounded-full', status.bg)}>
<StatusIcon size={12} className={status.color} />
<span className={cn('text-xs font-medium', status.color)}>
{status.label}
</span>
</div>
</div>
{/* Icon */}
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors">
<Icon size={24} className="text-primary" />
</div>
{/* Info */}
<h3 className="font-semibold text-text-primary mb-1">{agent.titel}</h3>
<p className="text-sm text-text-secondary mb-3 line-clamp-2">
{agent.kompetenser.slice(0, 3).join(', ')}
</p>
{/* Capabilities */}
<div className="flex flex-wrap gap-1.5">
{agent.capabilities.map(cap => (
<span
key={cap}
className="text-xs px-2 py-0.5 rounded-md bg-bg text-text-secondary"
>
{cap}
</span>
))}
</div>
{/* Hover Action */}
<div className="mt-4 pt-4 border-t border-border opacity-0 group-hover:opacity-100 transition-opacity">
<div className="flex items-center gap-2 text-sm text-primary">
<Sparkles size={14} />
<span>Öppna agent</span>
</div>
</div>
</motion.div>
);
})}
</div>
{/* Agent Chat */}
{selectedAgent && (
<AgentChat
rum={selectedAgent}
isOpen={chatOpen}
onToggle={() => setChatOpen(!chatOpen)}
/>
)}
</div>
);
}