@renseiai/agentfactory-dashboard 0.8.8 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renseiai/agentfactory-dashboard",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "Premium dashboard UI components for AgentFactory",
5
5
  "author": "Rensei AI (https://rensei.ai)",
6
6
  "license": "MIT",
@@ -0,0 +1,233 @@
1
+ 'use client'
2
+
3
+ import { cn } from '../../lib/utils'
4
+ import { useRoutingMetrics } from '../../hooks/use-routing-metrics'
5
+ import { EmptyState } from '../../components/shared/empty-state'
6
+ import { Skeleton } from '../../components/ui/skeleton'
7
+ import { formatCost } from '../../lib/format'
8
+ import { Router, Activity, TrendingUp, CheckCircle2, XCircle, HelpCircle } from 'lucide-react'
9
+
10
+ interface RoutingMetricsProps {
11
+ className?: string
12
+ }
13
+
14
+ function confidenceColor(confidence: number): string {
15
+ if (confidence > 0.7) return 'text-emerald-400'
16
+ if (confidence >= 0.3) return 'text-amber-400'
17
+ return 'text-red-400'
18
+ }
19
+
20
+ function confidenceBg(confidence: number): string {
21
+ if (confidence > 0.7) return 'bg-emerald-500/10 border-emerald-500/20'
22
+ if (confidence >= 0.3) return 'bg-amber-500/10 border-amber-500/20'
23
+ return 'bg-red-500/10 border-red-500/20'
24
+ }
25
+
26
+ export function RoutingMetrics({ className }: RoutingMetricsProps) {
27
+ const { data, isLoading } = useRoutingMetrics()
28
+
29
+ const routingDisabled = !isLoading && (!data || !data.summary.routingEnabled)
30
+
31
+ if (routingDisabled) {
32
+ return (
33
+ <div className={cn('p-6', className)}>
34
+ <EmptyState
35
+ title="Routing not enabled"
36
+ description="Multi-armed bandit routing will appear here once providers begin receiving observations."
37
+ icon={<Router className="h-8 w-8" />}
38
+ />
39
+ </div>
40
+ )
41
+ }
42
+
43
+ return (
44
+ <div className={cn('space-y-8 p-6', className)}>
45
+ {/* Section: Summary */}
46
+ <div>
47
+ <div className="mb-4 flex items-center gap-3">
48
+ <h2 className="font-display text-lg font-bold text-af-text-primary tracking-tight">
49
+ Routing Analytics
50
+ </h2>
51
+ {!isLoading && data && (
52
+ <span className="text-2xs font-body text-af-text-tertiary tabular-nums">
53
+ {data.summary.totalObservations} observation{data.summary.totalObservations !== 1 ? 's' : ''} total
54
+ </span>
55
+ )}
56
+ </div>
57
+
58
+ {isLoading ? (
59
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
60
+ {Array.from({ length: 4 }).map((_, i) => (
61
+ <Skeleton key={i} className="h-[72px] rounded-xl" />
62
+ ))}
63
+ </div>
64
+ ) : data ? (
65
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
66
+ <div className="rounded-xl border border-af-surface-border/50 bg-af-surface/40 p-4">
67
+ <p className="text-2xs font-body text-af-text-tertiary">Total Observations</p>
68
+ <p className="mt-1 font-display text-xl font-bold text-af-text-primary tabular-nums">
69
+ {data.summary.totalObservations}
70
+ </p>
71
+ </div>
72
+ <div className="rounded-xl border border-af-surface-border/50 bg-af-surface/40 p-4">
73
+ <p className="text-2xs font-body text-af-text-tertiary">Avg Confidence</p>
74
+ <p className={cn('mt-1 font-display text-xl font-bold tabular-nums', confidenceColor(data.summary.avgConfidence))}>
75
+ {(data.summary.avgConfidence * 100).toFixed(1)}%
76
+ </p>
77
+ </div>
78
+ <div className="rounded-xl border border-af-surface-border/50 bg-af-surface/40 p-4">
79
+ <p className="text-2xs font-body text-af-text-tertiary">Exploration Rate</p>
80
+ <p className="mt-1 font-display text-xl font-bold text-af-text-primary tabular-nums">
81
+ {(data.summary.explorationRate * 100).toFixed(0)}%
82
+ </p>
83
+ </div>
84
+ <div className="rounded-xl border border-af-surface-border/50 bg-af-surface/40 p-4">
85
+ <p className="text-2xs font-body text-af-text-tertiary">Providers Tracked</p>
86
+ <p className="mt-1 font-display text-xl font-bold text-af-text-primary tabular-nums">
87
+ {data.posteriors.length}
88
+ </p>
89
+ </div>
90
+ </div>
91
+ ) : null}
92
+ </div>
93
+
94
+ {/* Section: Provider Performance Matrix */}
95
+ <div>
96
+ <div className="mb-4 flex items-center gap-3">
97
+ <TrendingUp className="h-4 w-4 text-af-text-tertiary" />
98
+ <h2 className="font-display text-lg font-bold text-af-text-primary tracking-tight">
99
+ Provider Performance
100
+ </h2>
101
+ </div>
102
+
103
+ {isLoading ? (
104
+ <Skeleton className="h-[200px] rounded-xl" />
105
+ ) : data && data.posteriors.length > 0 ? (
106
+ <div className="overflow-x-auto rounded-xl border border-af-surface-border/50 bg-af-surface/40">
107
+ <table className="w-full text-left">
108
+ <thead>
109
+ <tr className="border-b border-af-surface-border/50">
110
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary">Provider</th>
111
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary">Work Type</th>
112
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary text-right">Expected Reward</th>
113
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary text-right">Confidence</th>
114
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary text-right">Observations</th>
115
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary text-right">Avg Cost</th>
116
+ <th className="px-4 py-3 text-2xs font-body font-medium text-af-text-tertiary text-right">Alpha / Beta</th>
117
+ </tr>
118
+ </thead>
119
+ <tbody>
120
+ {data.posteriors.map((p) => (
121
+ <tr
122
+ key={`${p.provider}-${p.workType}`}
123
+ className="border-b border-af-surface-border/30 last:border-b-0 hover:bg-af-surface/60 transition-colors"
124
+ >
125
+ <td className="px-4 py-3 text-xs font-body font-medium text-af-text-primary">{p.provider}</td>
126
+ <td className="px-4 py-3 text-xs font-body text-af-text-secondary">{p.workType}</td>
127
+ <td className="px-4 py-3 text-xs font-body text-af-text-primary text-right tabular-nums">
128
+ {(p.expectedReward * 100).toFixed(1)}%
129
+ </td>
130
+ <td className="px-4 py-3 text-right">
131
+ <span
132
+ className={cn(
133
+ 'inline-block rounded-md border px-2 py-0.5 text-xs font-body tabular-nums',
134
+ confidenceBg(p.confidence),
135
+ confidenceColor(p.confidence),
136
+ )}
137
+ >
138
+ {(p.confidence * 100).toFixed(1)}%
139
+ </span>
140
+ </td>
141
+ <td className="px-4 py-3 text-xs font-body text-af-text-secondary text-right tabular-nums">
142
+ {p.totalObservations}
143
+ </td>
144
+ <td className="px-4 py-3 text-xs font-body text-af-text-secondary text-right tabular-nums">
145
+ {formatCost(p.avgCostUsd)}
146
+ </td>
147
+ <td className="px-4 py-3 text-xs font-body text-af-text-tertiary text-right tabular-nums">
148
+ {p.alpha.toFixed(1)} / {p.beta.toFixed(1)}
149
+ </td>
150
+ </tr>
151
+ ))}
152
+ </tbody>
153
+ </table>
154
+ </div>
155
+ ) : (
156
+ <EmptyState
157
+ title="No provider data"
158
+ description="Posterior distributions will appear once routing observations are recorded."
159
+ />
160
+ )}
161
+ </div>
162
+
163
+ {/* Section: Recent Routing Decisions */}
164
+ <div>
165
+ <div className="mb-4 flex items-center gap-3">
166
+ <Activity className="h-4 w-4 text-af-text-tertiary" />
167
+ <h2 className="font-display text-lg font-bold text-af-text-primary tracking-tight">
168
+ Recent Decisions
169
+ </h2>
170
+ {!isLoading && data && (
171
+ <span className="text-2xs font-body text-af-text-tertiary tabular-nums">
172
+ last {data.recentDecisions.length}
173
+ </span>
174
+ )}
175
+ </div>
176
+
177
+ {isLoading ? (
178
+ <div className="space-y-2">
179
+ {Array.from({ length: 5 }).map((_, i) => (
180
+ <Skeleton key={i} className="h-[52px] rounded-xl" />
181
+ ))}
182
+ </div>
183
+ ) : data && data.recentDecisions.length > 0 ? (
184
+ <div className="space-y-2">
185
+ {data.recentDecisions.map((d, i) => (
186
+ <div
187
+ key={`${d.timestamp}-${i}`}
188
+ className="flex items-center gap-4 rounded-xl border border-af-surface-border/50 bg-af-surface/40 px-4 py-3"
189
+ >
190
+ <div className="flex-shrink-0">
191
+ {d.taskCompleted ? (
192
+ <CheckCircle2 className="h-4 w-4 text-emerald-400" />
193
+ ) : d.reward > 0 ? (
194
+ <HelpCircle className="h-4 w-4 text-amber-400" />
195
+ ) : (
196
+ <XCircle className="h-4 w-4 text-red-400" />
197
+ )}
198
+ </div>
199
+ <div className="min-w-0 flex-1">
200
+ <div className="flex items-center gap-2">
201
+ <span className="text-xs font-body font-medium text-af-text-primary">{d.provider}</span>
202
+ <span className="text-2xs font-body text-af-text-tertiary">{d.workType}</span>
203
+ {d.explorationReason && (
204
+ <span className="rounded-full bg-af-surface/60 px-1.5 py-0.5 text-2xs font-body text-af-text-tertiary">
205
+ {d.explorationReason}
206
+ </span>
207
+ )}
208
+ </div>
209
+ </div>
210
+ <div className="flex items-center gap-4 flex-shrink-0">
211
+ <span className={cn('text-xs font-body tabular-nums', confidenceColor(d.confidence))}>
212
+ {(d.confidence * 100).toFixed(0)}%
213
+ </span>
214
+ <span className="text-xs font-body text-af-text-secondary tabular-nums">
215
+ reward {d.reward.toFixed(2)}
216
+ </span>
217
+ <span className="text-2xs font-body text-af-text-tertiary tabular-nums">
218
+ {new Date(d.timestamp).toLocaleTimeString()}
219
+ </span>
220
+ </div>
221
+ </div>
222
+ ))}
223
+ </div>
224
+ ) : (
225
+ <EmptyState
226
+ title="No recent decisions"
227
+ description="Routing decisions will appear here as agents complete tasks."
228
+ />
229
+ )}
230
+ </div>
231
+ </div>
232
+ )
233
+ }
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+
3
+ import useSWR from 'swr'
4
+ import type { PublicRoutingMetricsResponse } from '../types/api'
5
+
6
+ const fetcher = (url: string) => fetch(url).then((r) => r.json())
7
+
8
+ export function useRoutingMetrics(refreshInterval = 10000) {
9
+ return useSWR<PublicRoutingMetricsResponse>('/api/public/routing-metrics', fetcher, {
10
+ refreshInterval,
11
+ dedupingInterval: 5000,
12
+ })
13
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export { SettingsPage } from './pages/settings-page'
12
12
 
13
13
  // Fleet components
14
14
  export { FleetOverview } from './components/fleet/fleet-overview'
15
+ export { RoutingMetrics } from './components/fleet/routing-metrics'
15
16
  export { AgentCard } from './components/fleet/agent-card'
16
17
  export { StatCard } from './components/fleet/stat-card'
17
18
  export { StatusDot } from './components/fleet/status-dot'
@@ -59,6 +60,7 @@ export {
59
60
  export { useStats } from './hooks/use-stats'
60
61
  export { useSessions } from './hooks/use-sessions'
61
62
  export { useWorkers } from './hooks/use-workers'
63
+ export { useRoutingMetrics } from './hooks/use-routing-metrics'
62
64
 
63
65
  // Utilities
64
66
  export { cn } from './lib/utils'
@@ -75,6 +77,10 @@ export type {
75
77
  WorkerResponse,
76
78
  WorkersListResponse,
77
79
  PipelineStatus,
80
+ PublicRoutingMetricsResponse,
81
+ RoutingPosteriorResponse,
82
+ RoutingDecisionResponse,
83
+ RoutingSummaryResponse,
78
84
  } from './types/api'
79
85
  export type { WorkTypeConfig } from './lib/work-type-config'
80
86
  export type { StatusConfig } from './lib/status-config'
package/src/types/api.ts CHANGED
@@ -46,3 +46,38 @@ export interface WorkersListResponse {
46
46
  }
47
47
 
48
48
  export type PipelineStatus = 'backlog' | 'started' | 'finished' | 'delivered' | 'accepted'
49
+
50
+ export interface RoutingPosteriorResponse {
51
+ provider: string
52
+ workType: string
53
+ alpha: number
54
+ beta: number
55
+ expectedReward: number
56
+ confidence: number
57
+ totalObservations: number
58
+ avgCostUsd: number
59
+ }
60
+
61
+ export interface RoutingDecisionResponse {
62
+ timestamp: number
63
+ provider: string
64
+ workType: string
65
+ reward: number
66
+ taskCompleted: boolean
67
+ confidence: number
68
+ explorationReason?: string
69
+ }
70
+
71
+ export interface RoutingSummaryResponse {
72
+ totalObservations: number
73
+ routingEnabled: boolean
74
+ explorationRate: number
75
+ avgConfidence: number
76
+ }
77
+
78
+ export interface PublicRoutingMetricsResponse {
79
+ posteriors: RoutingPosteriorResponse[]
80
+ recentDecisions: RoutingDecisionResponse[]
81
+ summary: RoutingSummaryResponse
82
+ timestamp: string
83
+ }