Phase 5.2+: per-agent usage лимиты на AgentCard

This commit is contained in:
ochenstarik-ui 2026-07-20 22:26:25 +07:00
parent cfa37e1723
commit 98ad890286
3 changed files with 52 additions and 11 deletions

View file

@ -8,6 +8,12 @@ class Agent(BaseModel):
runtime: str = "?" runtime: str = "?"
model: str = "?" model: str = "?"
healthy: bool = True healthy: bool = True
cpu: float = 0
active_runs: int = 0
tokens_used: int = 0
tokens_limit: int = 100000
budget_used: float = 0
budget_limit: float = 50
class AgentUpdate(BaseModel): class AgentUpdate(BaseModel):
status: Literal["online", "offline", "busy"] status: Literal["online", "offline", "busy"]

View file

@ -1,20 +1,24 @@
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Cpu, Activity, Zap } from 'lucide-react' import { Cpu, Activity, Zap, Wallet } from 'lucide-react'
import { Badge } from '@/components/ui/badge' import { Progress } from '@/components/ui/progress'
export function AgentCard({ agent }: { agent: any }) { export function AgentCard({ agent }: { agent: any }) {
const statusColor = { const statusColor = {
online: 'bg-emerald-500', online: 'bg-emerald-500',
busy: 'bg-amber-500', busy: 'bg-amber-500',
offline: 'bg-zinc-500', offline: 'bg-zinc-500',
}[agent.status] || 'bg-zinc-500' }[agent.status] || 'bg-emerald-500'
const tokenPercent = Math.round((agent.tokens_used / agent.tokens_limit) * 100)
const budgetPercent = Math.round((agent.budget_used / agent.budget_limit) * 100)
return ( return (
<motion.div <motion.div
whileHover={{ scale: 1.02, y: -4 }} whileHover={{ scale: 1.02, y: -4 }}
className="group bg-card/80 border border-border hover:border-primary/50 rounded-3xl p-6 transition-all duration-300 backdrop-blur-xl" className="group bg-card/80 border border-border hover:border-primary/50 rounded-3xl p-6 transition-all duration-300 backdrop-blur-xl"
> >
<div className="flex items-start justify-between"> {/* Header */}
<div className="flex items-start justify-between mb-6">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className={`w-5 h-5 rounded-2xl ${statusColor} flex-shrink-0 ring-4 ring-background`} /> <div className={`w-5 h-5 rounded-2xl ${statusColor} flex-shrink-0 ring-4 ring-background`} />
<div> <div>
@ -22,10 +26,11 @@ export function AgentCard({ agent }: { agent: any }) {
<div className="text-muted-foreground text-sm">{agent.runtime} · {agent.model}</div> <div className="text-muted-foreground text-sm">{agent.runtime} · {agent.model}</div>
</div> </div>
</div> </div>
<Badge variant="outline">{agent.status}</Badge> <div className="text-xs uppercase tracking-widest text-emerald-500 font-medium">{agent.status}</div>
</div> </div>
<div className="mt-8 flex gap-8"> {/* Main metrics */}
<div className="grid grid-cols-2 gap-6 mb-6">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Cpu className="w-6 h-6 text-muted-foreground" /> <Cpu className="w-6 h-6 text-muted-foreground" />
<div> <div>
@ -33,16 +38,42 @@ export function AgentCard({ agent }: { agent: any }) {
<div className="text-[10px] uppercase tracking-widest text-muted-foreground -mt-1">CPU %</div> <div className="text-[10px] uppercase tracking-widest text-muted-foreground -mt-1">CPU %</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Activity className="w-6 h-6 text-muted-foreground" /> <Activity className="w-6 h-6 text-muted-foreground" />
<div> <div>
<div className="text-3xl font-mono font-semibold tabular-nums">{agent.activeRuns}</div> <div className="text-3xl font-mono font-semibold tabular-nums">{agent.active_runs || agent.activeRuns}</div>
<div className="text-[10px] uppercase tracking-widest text-muted-foreground -mt-1">RUNS</div> <div className="text-[10px] uppercase tracking-widest text-muted-foreground -mt-1">RUNS</div>
</div> </div>
</div> </div>
</div> </div>
{/* Per-agent Usage Limits */}
<div className="space-y-4 text-sm border-t border-border pt-5">
<div>
<div className="flex justify-between mb-1.5">
<span className="flex items-center gap-1.5 text-muted-foreground">
<Zap className="w-4 h-4" /> Tokens
</span>
<span className="font-mono text-muted-foreground text-xs">
{agent.tokens_used?.toLocaleString()} / {agent.tokens_limit?.toLocaleString()}
</span>
</div>
<Progress value={tokenPercent} />
</div>
<div>
<div className="flex justify-between mb-1.5">
<span className="flex items-center gap-1.5 text-muted-foreground">
<Wallet className="w-4 h-4" /> Budget
</span>
<span className="font-mono text-muted-foreground text-xs">
${agent.budget_used} / ${agent.budget_limit}
</span>
</div>
<Progress value={budgetPercent} />
</div>
</div>
<button <button
onClick={() => alert('Handoff started for ' + agent.name)} onClick={() => alert('Handoff started for ' + agent.name)}
className="mt-6 w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 rounded-2xl text-sm font-medium transition-colors flex items-center justify-center gap-2" className="mt-6 w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 rounded-2xl text-sm font-medium transition-colors flex items-center justify-center gap-2"

View file

@ -10,15 +10,19 @@ export function AgentGrid() {
queryFn: async () => { queryFn: async () => {
const r = await fetch('/api/v1/agents/health') const r = await fetch('/api/v1/agents/health')
const raw = await r.json() const raw = await r.json()
const mapped = Object.entries(raw).map(([id, info]: [string, any]) => ({ const mapped = Object.entries(raw).map(([id, info]: [string, any], i: number) => ({
id, id,
name: id.replace('worker-', ''), name: id.replace('worker-', ''),
status: info.healthy ? ('online' as const) : ('busy' as const), status: info.healthy ? ('online' as const) : ('busy' as const),
runtime: info.model?.split('/')[0] || '?', runtime: info.model?.split('/')[0] || '?',
model: info.model?.split('/').pop() || '?', model: info.model?.split('/').pop() || '?',
healthy: info.healthy, healthy: info.healthy,
cpu: Math.floor(Math.random() * 60 + 10), cpu: [31, 60, 18, 72][i] || 50,
activeRuns: Math.floor(Math.random() * 5), activeRuns: [4, 0, 2, 1][i] || 0,
tokens_used: [45800, 124800, 22000, 89000][i] || 50000,
tokens_limit: 200000,
budget_used: [12.4, 42.5, 5.2, 28.0][i] || 20,
budget_limit: 100,
})) }))
setAgents(mapped) setAgents(mapped)
return mapped return mapped