groove-dev 0.24.0 → 0.24.2
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/node_modules/@groove-dev/gui/dist/assets/index-AXS9JOJD.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-z5gtKpHQ.js +587 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +10 -13
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +13 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/header-bar.jsx +11 -11
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +44 -44
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +7 -7
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +13 -17
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +48 -25
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +18 -39
- package/package.json +1 -1
- package/packages/gui/dist/assets/index-AXS9JOJD.css +1 -0
- package/packages/gui/dist/assets/index-z5gtKpHQ.js +587 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/components/dashboard/activity-feed.jsx +9 -9
- package/packages/gui/src/components/dashboard/cache-ring.jsx +10 -13
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +13 -14
- package/packages/gui/src/components/dashboard/header-bar.jsx +11 -11
- package/packages/gui/src/components/dashboard/intel-panel.jsx +44 -44
- package/packages/gui/src/components/dashboard/kpi-card.jsx +7 -7
- package/packages/gui/src/components/dashboard/routing-chart.jsx +13 -17
- package/packages/gui/src/components/dashboard/token-chart.jsx +48 -25
- package/packages/gui/src/views/dashboard.jsx +18 -39
- package/node_modules/@groove-dev/gui/dist/assets/index-CqdQP7yG.js +0 -587
- package/node_modules/@groove-dev/gui/dist/assets/index-DvcNOnKP.css +0 -1
- package/packages/gui/dist/assets/index-CqdQP7yG.js +0 -587
- package/packages/gui/dist/assets/index-DvcNOnKP.css +0 -1
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Groove GUI</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-z5gtKpHQ.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-AXS9JOJD.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
|
@@ -12,17 +12,17 @@ const ICONS = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
const ICON_COLORS = {
|
|
15
|
-
spawn: '
|
|
16
|
-
rotation: '
|
|
17
|
-
completion: '
|
|
18
|
-
error: '
|
|
19
|
-
default: '
|
|
15
|
+
spawn: 'text-accent',
|
|
16
|
+
rotation: 'text-purple',
|
|
17
|
+
completion: 'text-success',
|
|
18
|
+
error: 'text-danger',
|
|
19
|
+
default: 'text-text-3',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const ActivityFeed = memo(function ActivityFeed({ events = [] }) {
|
|
23
23
|
if (!events.length) {
|
|
24
24
|
return (
|
|
25
|
-
<div className="text-
|
|
25
|
+
<div className="text-xs text-text-3 font-mono py-2.5 text-center">
|
|
26
26
|
No recent activity
|
|
27
27
|
</div>
|
|
28
28
|
);
|
|
@@ -35,9 +35,9 @@ const ActivityFeed = memo(function ActivityFeed({ events = [] }) {
|
|
|
35
35
|
const color = ICON_COLORS[event.type] || ICON_COLORS.default;
|
|
36
36
|
return (
|
|
37
37
|
<div key={i} className="flex items-center gap-1.5 flex-shrink-0">
|
|
38
|
-
<Icon size={
|
|
39
|
-
<span className="text-
|
|
40
|
-
<span className="text-
|
|
38
|
+
<Icon size={11} className={color} />
|
|
39
|
+
<span className="text-xs font-sans text-text-2 whitespace-nowrap">{event.text}</span>
|
|
40
|
+
<span className="text-2xs font-mono text-text-4">{timeAgo(event.timestamp || event.t)}</span>
|
|
41
41
|
</div>
|
|
42
42
|
);
|
|
43
43
|
})}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useRef, useEffect, memo } from 'react';
|
|
3
|
-
import { HEX
|
|
4
|
-
import { fmtNum
|
|
3
|
+
import { HEX } from '../../lib/theme-hex';
|
|
4
|
+
import { fmtNum } from '../../lib/format';
|
|
5
5
|
|
|
6
6
|
const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, totalInput = 0, size = 140 }) {
|
|
7
7
|
const canvasRef = useRef(null);
|
|
@@ -23,7 +23,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
|
|
|
23
23
|
const radius = (size - 12) / 2;
|
|
24
24
|
const strokeWidth = 5;
|
|
25
25
|
|
|
26
|
-
// Arc spans 270 degrees (135° to 405°)
|
|
27
26
|
const startAngle = (135 * Math.PI) / 180;
|
|
28
27
|
const endAngle = (405 * Math.PI) / 180;
|
|
29
28
|
const sweep = endAngle - startAngle;
|
|
@@ -40,7 +39,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
|
|
|
40
39
|
const readPct = cacheRead / total;
|
|
41
40
|
const createPct = cacheCreation / total;
|
|
42
41
|
|
|
43
|
-
// Segment 1: Cache Read (accent)
|
|
44
42
|
if (readPct > 0) {
|
|
45
43
|
const segEnd = startAngle + sweep * readPct;
|
|
46
44
|
ctx.beginPath();
|
|
@@ -51,7 +49,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
|
|
|
51
49
|
ctx.stroke();
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
// Segment 2: Cache Creation (purple)
|
|
55
52
|
if (createPct > 0) {
|
|
56
53
|
const segStart = startAngle + sweep * readPct;
|
|
57
54
|
const segEnd = segStart + sweep * createPct;
|
|
@@ -64,16 +61,16 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
|
|
|
64
61
|
}
|
|
65
62
|
}
|
|
66
63
|
|
|
67
|
-
// Center text
|
|
64
|
+
// Center text
|
|
68
65
|
ctx.textAlign = 'center';
|
|
69
66
|
ctx.textBaseline = 'middle';
|
|
70
|
-
ctx.font = `600 ${size * 0.
|
|
67
|
+
ctx.font = `600 ${size * 0.2}px 'JetBrains Mono Variable', monospace`;
|
|
71
68
|
ctx.fillStyle = HEX.text0;
|
|
72
69
|
ctx.fillText(`${Math.round(hitRate)}%`, cx, cy - 3);
|
|
73
70
|
|
|
74
|
-
ctx.font = `500 ${size * 0.
|
|
75
|
-
ctx.fillStyle = HEX.
|
|
76
|
-
ctx.fillText('CACHE', cx, cy + size * 0.
|
|
71
|
+
ctx.font = `500 ${size * 0.08}px 'JetBrains Mono Variable', monospace`;
|
|
72
|
+
ctx.fillStyle = HEX.text3;
|
|
73
|
+
ctx.fillText('CACHE', cx, cy + size * 0.13);
|
|
77
74
|
}, [cacheRead, cacheCreation, totalInput, size, total, hitRate]);
|
|
78
75
|
|
|
79
76
|
return (
|
|
@@ -94,10 +91,10 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
|
|
|
94
91
|
|
|
95
92
|
function StatRow({ color, label, value }) {
|
|
96
93
|
return (
|
|
97
|
-
<div className="flex items-center gap-2 text-
|
|
94
|
+
<div className="flex items-center gap-2 text-xs font-mono">
|
|
98
95
|
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: color }} />
|
|
99
|
-
<span className="text-
|
|
100
|
-
<span className="text-
|
|
96
|
+
<span className="text-text-3 uppercase tracking-wider flex-1">{label}</span>
|
|
97
|
+
<span className="text-text-1 tabular-nums">{value}</span>
|
|
101
98
|
</div>
|
|
102
99
|
);
|
|
103
100
|
}
|
|
@@ -15,7 +15,7 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
|
|
|
15
15
|
const successRate = quality?.toolSuccessRate != null ? Math.round(quality.toolSuccessRate * 100) : null;
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<div className="flex items-center gap-2 px-3 py-1.5 hover:bg-
|
|
18
|
+
<div className="flex items-center gap-2 px-3 py-1.5 hover:bg-surface-3 transition-colors">
|
|
19
19
|
{/* Status square */}
|
|
20
20
|
<span className="relative flex-shrink-0 w-[6px] h-[6px]">
|
|
21
21
|
<span className="absolute inset-0 rounded-sm" style={{ background: sColor }} />
|
|
@@ -29,26 +29,26 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
|
|
|
29
29
|
|
|
30
30
|
{/* Name + role/model */}
|
|
31
31
|
<div className="flex-1 min-w-0">
|
|
32
|
-
<div className="text-
|
|
32
|
+
<div className="text-xs font-semibold text-text-0 font-sans truncate leading-none">{agent.name}</div>
|
|
33
33
|
<div className="flex items-center gap-1 mt-0.5">
|
|
34
|
-
<span className="text-
|
|
35
|
-
<span className="text-
|
|
36
|
-
<span className="text-
|
|
34
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider">{agent.role}</span>
|
|
35
|
+
<span className="text-2xs text-text-4">/</span>
|
|
36
|
+
<span className="text-2xs font-mono text-text-3">{shortModel(agent.model)}</span>
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
39
39
|
|
|
40
40
|
{/* Tokens + cost */}
|
|
41
41
|
<div className="text-right flex-shrink-0">
|
|
42
|
-
<div className="text-
|
|
42
|
+
<div className="text-xs font-mono text-text-1 tabular-nums leading-none">{fmtNum(agent.tokens || 0)}</div>
|
|
43
43
|
{(agent.costUsd || 0) > 0 && (
|
|
44
|
-
<div className="text-
|
|
44
|
+
<div className="text-2xs font-mono text-text-3 mt-0.5">{fmtDollar(agent.costUsd)}</div>
|
|
45
45
|
)}
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
{/* Quality / tool success */}
|
|
49
49
|
{successRate != null && (
|
|
50
50
|
<span
|
|
51
|
-
className="text-
|
|
51
|
+
className="text-2xs font-mono font-bold uppercase px-1 py-px rounded-sm flex-shrink-0"
|
|
52
52
|
style={{
|
|
53
53
|
color: successRate >= 90 ? '#4ae168' : successRate >= 70 ? '#e5c07b' : '#e06c75',
|
|
54
54
|
background: successRate >= 90 ? 'rgba(74,225,104,0.1)' : successRate >= 70 ? 'rgba(229,192,123,0.1)' : 'rgba(224,108,117,0.1)',
|
|
@@ -60,7 +60,7 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
|
|
|
60
60
|
|
|
61
61
|
{/* Cost source badge */}
|
|
62
62
|
{agent.costSource && agent.costSource !== 'actual' && (
|
|
63
|
-
<span className="text-
|
|
63
|
+
<span className="text-2xs font-mono text-text-4 uppercase tracking-wider flex-shrink-0">
|
|
64
64
|
{COST_SOURCE_LABEL[agent.costSource] || ''}
|
|
65
65
|
</span>
|
|
66
66
|
)}
|
|
@@ -68,9 +68,9 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
|
|
|
68
68
|
{/* Context bar */}
|
|
69
69
|
<div className="w-12 flex-shrink-0">
|
|
70
70
|
<div className="flex items-center justify-end gap-1 mb-0.5">
|
|
71
|
-
<span className="text-
|
|
71
|
+
<span className="text-2xs font-mono text-text-2 tabular-nums">{contextPct}%</span>
|
|
72
72
|
</div>
|
|
73
|
-
<div className="h-[2px] bg-
|
|
73
|
+
<div className="h-[2px] bg-surface-0 rounded-full overflow-hidden">
|
|
74
74
|
<div
|
|
75
75
|
className="h-full rounded-full transition-all duration-700"
|
|
76
76
|
style={{
|
|
@@ -95,13 +95,12 @@ function shortModel(id) {
|
|
|
95
95
|
const FleetPanel = memo(function FleetPanel({ agentBreakdown, rotating = [] }) {
|
|
96
96
|
if (!agentBreakdown?.length) {
|
|
97
97
|
return (
|
|
98
|
-
<div className="flex-1 flex items-center justify-center text-
|
|
98
|
+
<div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
|
|
99
99
|
No agents
|
|
100
100
|
</div>
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// Group by team
|
|
105
104
|
const teams = {};
|
|
106
105
|
for (const a of agentBreakdown) {
|
|
107
106
|
const team = a.teamId || 'ungrouped';
|
|
@@ -117,7 +116,7 @@ const FleetPanel = memo(function FleetPanel({ agentBreakdown, rotating = [] }) {
|
|
|
117
116
|
{Object.entries(teams).map(([team, members]) => (
|
|
118
117
|
<div key={team}>
|
|
119
118
|
<div className="px-3 pt-2 pb-1">
|
|
120
|
-
<span className="text-
|
|
119
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">{team}</span>
|
|
121
120
|
</div>
|
|
122
121
|
{members.map((a) => (
|
|
123
122
|
<AgentRow key={a.id} agent={a} isRotating={rotatingSet.has(a.id)} />
|
|
@@ -6,31 +6,31 @@ import { RefreshCw } from 'lucide-react';
|
|
|
6
6
|
|
|
7
7
|
const DashboardHeader = memo(function DashboardHeader({ connected, runningCount, totalCount, uptime, lastFetch, activeTeam }) {
|
|
8
8
|
return (
|
|
9
|
-
<div className="flex items-center gap-4 px-4 py-2 bg-surface-1 border-b border-
|
|
10
|
-
<h2 className="text-
|
|
9
|
+
<div className="flex items-center gap-4 px-4 py-2 bg-surface-1 border-b border-border">
|
|
10
|
+
<h2 className="text-xs font-semibold text-text-0 font-sans tracking-wide uppercase">Command Center</h2>
|
|
11
11
|
|
|
12
12
|
{activeTeam && (
|
|
13
13
|
<>
|
|
14
|
-
<span className="text-
|
|
15
|
-
<span className="text-
|
|
14
|
+
<span className="text-text-4">/</span>
|
|
15
|
+
<span className="text-xs font-mono text-text-2">{activeTeam.name}</span>
|
|
16
16
|
</>
|
|
17
17
|
)}
|
|
18
18
|
|
|
19
19
|
<div className="flex-1" />
|
|
20
20
|
|
|
21
21
|
{connected && (
|
|
22
|
-
<div className="flex items-center gap-3.5 text-
|
|
22
|
+
<div className="flex items-center gap-3.5 text-xs font-mono text-text-2">
|
|
23
23
|
<span>
|
|
24
|
-
<span className="text-
|
|
25
|
-
<span className="text-
|
|
26
|
-
<span className="ml-1">agents</span>
|
|
24
|
+
<span className="text-text-1">{runningCount}</span>
|
|
25
|
+
<span className="text-text-3">/{totalCount}</span>
|
|
26
|
+
<span className="ml-1 text-text-3">agents</span>
|
|
27
27
|
</span>
|
|
28
28
|
{uptime > 0 && (
|
|
29
|
-
<span>Up {fmtUptime(uptime)}</span>
|
|
29
|
+
<span className="text-text-3">Up {fmtUptime(uptime)}</span>
|
|
30
30
|
)}
|
|
31
31
|
{lastFetch > 0 && (
|
|
32
|
-
<span className="flex items-center gap-1 text-
|
|
33
|
-
<RefreshCw size={
|
|
32
|
+
<span className="flex items-center gap-1 text-text-4">
|
|
33
|
+
<RefreshCw size={9} />
|
|
34
34
|
<span>{timeAgo(lastFetch)}</span>
|
|
35
35
|
</span>
|
|
36
36
|
)}
|
|
@@ -23,7 +23,7 @@ function TinySparkline({ data, color = HEX.accent, width = 60, height = 16 }) {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<svg width={width} height={height} className="flex-shrink-0">
|
|
26
|
-
<polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeLinejoin="round" strokeOpacity="0.
|
|
26
|
+
<polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeLinejoin="round" strokeOpacity="0.7" />
|
|
27
27
|
</svg>
|
|
28
28
|
);
|
|
29
29
|
}
|
|
@@ -33,11 +33,11 @@ function SavingsBar({ label, value, total, color }) {
|
|
|
33
33
|
const pct = total > 0 ? (value / total) * 100 : 0;
|
|
34
34
|
return (
|
|
35
35
|
<div className="space-y-0.5">
|
|
36
|
-
<div className="flex items-center justify-between text-
|
|
37
|
-
<span className="text-
|
|
38
|
-
<span className="text-
|
|
36
|
+
<div className="flex items-center justify-between text-xs font-mono">
|
|
37
|
+
<span className="text-text-2">{label}</span>
|
|
38
|
+
<span className="text-text-1 tabular-nums">{fmtNum(value)}</span>
|
|
39
39
|
</div>
|
|
40
|
-
<div className="h-[2px] bg-
|
|
40
|
+
<div className="h-[2px] bg-surface-0 rounded-full overflow-hidden">
|
|
41
41
|
<div
|
|
42
42
|
className="h-full rounded-full transition-all duration-500"
|
|
43
43
|
style={{ width: `${Math.min(pct, 100)}%`, background: color }}
|
|
@@ -60,18 +60,18 @@ function RotationTab({ tokens, rotation }) {
|
|
|
60
60
|
{/* Big numbers */}
|
|
61
61
|
<div className="flex gap-4">
|
|
62
62
|
<div>
|
|
63
|
-
<div className="text-
|
|
64
|
-
<div className="text-
|
|
63
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Rotations</div>
|
|
64
|
+
<div className="text-xl font-mono font-semibold text-text-0 tabular-nums leading-none">
|
|
65
65
|
{rotation?.totalRotations || 0}
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
<div>
|
|
69
|
-
<div className="text-
|
|
70
|
-
<div className="text-
|
|
69
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Saved</div>
|
|
70
|
+
<div className="text-xl font-mono font-semibold text-success tabular-nums leading-none">
|
|
71
71
|
{fmtNum(totalSaved)}
|
|
72
72
|
</div>
|
|
73
73
|
{hypothetical > 0 && (
|
|
74
|
-
<div className="text-
|
|
74
|
+
<div className="text-2xs font-mono text-text-3 mt-0.5">
|
|
75
75
|
{fmtPct((totalSaved / hypothetical) * 100)} of total
|
|
76
76
|
</div>
|
|
77
77
|
)}
|
|
@@ -88,13 +88,13 @@ function RotationTab({ tokens, rotation }) {
|
|
|
88
88
|
{/* Rotation history */}
|
|
89
89
|
{rotation?.history?.length > 0 && (
|
|
90
90
|
<div>
|
|
91
|
-
<div className="text-
|
|
91
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1.5">Recent</div>
|
|
92
92
|
<div className="space-y-1">
|
|
93
93
|
{rotation.history.slice(-8).reverse().map((r, i) => (
|
|
94
|
-
<div key={i} className="flex items-center gap-2 text-
|
|
95
|
-
<span className="text-
|
|
96
|
-
<span className="text-
|
|
97
|
-
<span className="text-
|
|
94
|
+
<div key={i} className="flex items-center gap-2 text-xs font-mono px-2 py-1 bg-surface-0 rounded">
|
|
95
|
+
<span className="text-text-1 font-medium capitalize truncate flex-1">{r.agentName || r.role}</span>
|
|
96
|
+
<span className="text-text-3 tabular-nums">{fmtPct((r.contextUsage || 0) * 100)}</span>
|
|
97
|
+
<span className="text-text-4">{timeAgo(r.timestamp)}</span>
|
|
98
98
|
</div>
|
|
99
99
|
))}
|
|
100
100
|
</div>
|
|
@@ -109,7 +109,7 @@ function RotationTab({ tokens, rotation }) {
|
|
|
109
109
|
function AdaptiveTab({ adaptive }) {
|
|
110
110
|
if (!adaptive?.length) {
|
|
111
111
|
return (
|
|
112
|
-
<div className="flex-1 flex items-center justify-center text-
|
|
112
|
+
<div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
|
|
113
113
|
No adaptive profiles
|
|
114
114
|
</div>
|
|
115
115
|
);
|
|
@@ -119,19 +119,19 @@ function AdaptiveTab({ adaptive }) {
|
|
|
119
119
|
<ScrollArea className="flex-1">
|
|
120
120
|
<div className="p-3 space-y-3">
|
|
121
121
|
{adaptive.map((p) => (
|
|
122
|
-
<div key={p.key} className="bg-
|
|
122
|
+
<div key={p.key} className="bg-surface-0 rounded px-2.5 py-2 space-y-1.5">
|
|
123
123
|
{/* Profile header */}
|
|
124
124
|
<div className="flex items-center gap-2">
|
|
125
|
-
<span className="text-
|
|
126
|
-
<span className="text-
|
|
125
|
+
<span className="text-xs font-mono text-text-1 flex-1 truncate">{p.key}</span>
|
|
126
|
+
<span className="text-xs font-mono font-semibold text-text-0 tabular-nums">
|
|
127
127
|
{fmtPct(p.threshold * 100)}
|
|
128
128
|
</span>
|
|
129
129
|
<span
|
|
130
130
|
className={cn(
|
|
131
|
-
'text-
|
|
131
|
+
'text-2xs font-mono font-bold uppercase px-1 py-px rounded-sm',
|
|
132
132
|
p.converged
|
|
133
|
-
? 'text-
|
|
134
|
-
: 'text-
|
|
133
|
+
? 'text-success bg-success/10'
|
|
134
|
+
: 'text-text-3 bg-surface-3',
|
|
135
135
|
)}
|
|
136
136
|
>
|
|
137
137
|
{p.converged ? 'CONV' : `${p.adjustments} adj`}
|
|
@@ -141,7 +141,7 @@ function AdaptiveTab({ adaptive }) {
|
|
|
141
141
|
{/* Threshold drift sparkline */}
|
|
142
142
|
{p.thresholdHistory?.length > 1 && (
|
|
143
143
|
<div className="flex items-center gap-2">
|
|
144
|
-
<span className="text-
|
|
144
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Drift</span>
|
|
145
145
|
<TinySparkline
|
|
146
146
|
data={p.thresholdHistory.map((h) => h.v)}
|
|
147
147
|
color={p.converged ? HEX.success : HEX.accent}
|
|
@@ -154,7 +154,7 @@ function AdaptiveTab({ adaptive }) {
|
|
|
154
154
|
{/* Quality scores sparkline */}
|
|
155
155
|
{p.recentScores?.length > 1 && (
|
|
156
156
|
<div className="flex items-center gap-2">
|
|
157
|
-
<span className="text-
|
|
157
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Quality</span>
|
|
158
158
|
<TinySparkline
|
|
159
159
|
data={p.recentScores}
|
|
160
160
|
color={HEX.warning}
|
|
@@ -174,7 +174,7 @@ function AdaptiveTab({ adaptive }) {
|
|
|
174
174
|
function JournalistTab({ journalist }) {
|
|
175
175
|
if (!journalist) {
|
|
176
176
|
return (
|
|
177
|
-
<div className="flex-1 flex items-center justify-center text-
|
|
177
|
+
<div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
|
|
178
178
|
Journalist inactive
|
|
179
179
|
</div>
|
|
180
180
|
);
|
|
@@ -186,19 +186,19 @@ function JournalistTab({ journalist }) {
|
|
|
186
186
|
{/* Status row */}
|
|
187
187
|
<div className="flex items-center gap-3">
|
|
188
188
|
<div>
|
|
189
|
-
<div className="text-
|
|
190
|
-
<div className="text-
|
|
189
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Cycles</div>
|
|
190
|
+
<div className="text-lg font-mono font-semibold text-text-0 tabular-nums leading-none">
|
|
191
191
|
{journalist.cycleCount || 0}
|
|
192
192
|
</div>
|
|
193
193
|
</div>
|
|
194
194
|
{journalist.lastCycleAt && (
|
|
195
195
|
<div>
|
|
196
|
-
<div className="text-
|
|
197
|
-
<div className="text-
|
|
196
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Last</div>
|
|
197
|
+
<div className="text-xs font-mono text-text-2">{timeAgo(journalist.lastCycleAt)}</div>
|
|
198
198
|
</div>
|
|
199
199
|
)}
|
|
200
200
|
{journalist.synthesizing && (
|
|
201
|
-
<span className="text-
|
|
201
|
+
<span className="text-2xs font-mono font-bold text-accent uppercase tracking-wider animate-pulse">
|
|
202
202
|
Synthesizing
|
|
203
203
|
</span>
|
|
204
204
|
)}
|
|
@@ -207,8 +207,8 @@ function JournalistTab({ journalist }) {
|
|
|
207
207
|
{/* Last summary */}
|
|
208
208
|
{journalist.lastSummary && (
|
|
209
209
|
<div>
|
|
210
|
-
<div className="text-
|
|
211
|
-
<div className="text-
|
|
210
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">Summary</div>
|
|
211
|
+
<div className="text-xs font-sans text-text-2 leading-relaxed bg-surface-0 rounded px-2.5 py-2 max-h-32 overflow-y-auto">
|
|
212
212
|
{journalist.lastSummary}
|
|
213
213
|
</div>
|
|
214
214
|
</div>
|
|
@@ -217,13 +217,13 @@ function JournalistTab({ journalist }) {
|
|
|
217
217
|
{/* Recent history */}
|
|
218
218
|
{journalist.recentHistory?.length > 0 && (
|
|
219
219
|
<div>
|
|
220
|
-
<div className="text-
|
|
220
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1.5">History</div>
|
|
221
221
|
<div className="space-y-1">
|
|
222
222
|
{journalist.recentHistory.slice().reverse().map((h, i) => (
|
|
223
|
-
<div key={i} className="flex items-center gap-2 text-
|
|
224
|
-
<span className="text-
|
|
225
|
-
<span className="text-
|
|
226
|
-
<span className="text-
|
|
223
|
+
<div key={i} className="flex items-center gap-2 text-xs font-mono px-2 py-1 bg-surface-0 rounded">
|
|
224
|
+
<span className="text-text-3">#{h.cycle}</span>
|
|
225
|
+
<span className="text-text-2 flex-1 truncate">{h.agentCount} agents</span>
|
|
226
|
+
<span className="text-text-4">{timeAgo(h.timestamp)}</span>
|
|
227
227
|
</div>
|
|
228
228
|
))}
|
|
229
229
|
</div>
|
|
@@ -238,17 +238,17 @@ function JournalistTab({ journalist }) {
|
|
|
238
238
|
const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journalist }) {
|
|
239
239
|
return (
|
|
240
240
|
<Tabs defaultValue="rotation" className="flex flex-col h-full">
|
|
241
|
-
<TabsList className="flex-shrink-0 px-1
|
|
242
|
-
<TabsTrigger value="rotation" className="text-
|
|
243
|
-
<RotateCw size={
|
|
241
|
+
<TabsList className="flex-shrink-0 px-1">
|
|
242
|
+
<TabsTrigger value="rotation" className="text-xs px-2.5 py-1.5 gap-1">
|
|
243
|
+
<RotateCw size={11} />
|
|
244
244
|
Rotation
|
|
245
245
|
</TabsTrigger>
|
|
246
|
-
<TabsTrigger value="adaptive" className="text-
|
|
247
|
-
<Brain size={
|
|
246
|
+
<TabsTrigger value="adaptive" className="text-xs px-2.5 py-1.5 gap-1">
|
|
247
|
+
<Brain size={11} />
|
|
248
248
|
Adaptive
|
|
249
249
|
</TabsTrigger>
|
|
250
|
-
<TabsTrigger value="journalist" className="text-
|
|
251
|
-
<Radio size={
|
|
250
|
+
<TabsTrigger value="journalist" className="text-xs px-2.5 py-1.5 gap-1">
|
|
251
|
+
<Radio size={11} />
|
|
252
252
|
Journalist
|
|
253
253
|
</TabsTrigger>
|
|
254
254
|
</TabsList>
|
|
@@ -22,12 +22,12 @@ function MiniSparkline({ data, color = HEX.accent, width = 72, height = 22 }) {
|
|
|
22
22
|
<svg width={width} height={height} className="flex-shrink-0">
|
|
23
23
|
<defs>
|
|
24
24
|
<linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
|
|
25
|
-
<stop offset="0%" stopColor={color} stopOpacity="0.
|
|
25
|
+
<stop offset="0%" stopColor={color} stopOpacity="0.2" />
|
|
26
26
|
<stop offset="100%" stopColor={color} stopOpacity="0" />
|
|
27
27
|
</linearGradient>
|
|
28
28
|
</defs>
|
|
29
29
|
<polygon points={`0,${height} ${points} ${width},${height}`} fill={`url(#${gradId})`} />
|
|
30
|
-
<polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeOpacity="0.
|
|
30
|
+
<polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeOpacity="0.8" />
|
|
31
31
|
</svg>
|
|
32
32
|
);
|
|
33
33
|
}
|
|
@@ -40,8 +40,8 @@ const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.acc
|
|
|
40
40
|
className,
|
|
41
41
|
)}>
|
|
42
42
|
<div className="flex-1 min-w-0">
|
|
43
|
-
<div className="text-
|
|
44
|
-
<div className="text-
|
|
43
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5 truncate">{label}</div>
|
|
44
|
+
<div className="text-base font-semibold font-mono text-text-0 tabular-nums leading-none">{value}</div>
|
|
45
45
|
</div>
|
|
46
46
|
<MiniSparkline data={sparkData} color={color} />
|
|
47
47
|
</div>
|
|
@@ -50,8 +50,8 @@ const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.acc
|
|
|
50
50
|
|
|
51
51
|
export function KpiStrip({ kpis }) {
|
|
52
52
|
return (
|
|
53
|
-
<div className="flex flex-wrap" style={{ background: '
|
|
54
|
-
{kpis.map((kpi
|
|
53
|
+
<div className="flex flex-wrap border-b border-border" style={{ background: 'var(--color-surface-0)' }}>
|
|
54
|
+
{kpis.map((kpi) => (
|
|
55
55
|
<KpiCard
|
|
56
56
|
key={kpi.label}
|
|
57
57
|
label={kpi.label}
|
|
@@ -60,7 +60,7 @@ export function KpiStrip({ kpis }) {
|
|
|
60
60
|
color={kpi.color}
|
|
61
61
|
className={cn(
|
|
62
62
|
'flex-1 basis-[12.5%] min-w-[140px]',
|
|
63
|
-
'border-b border-r border-
|
|
63
|
+
'border-b border-r border-border',
|
|
64
64
|
)}
|
|
65
65
|
/>
|
|
66
66
|
))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useRef, useEffect, memo } from 'react';
|
|
3
|
-
import { HEX
|
|
3
|
+
import { HEX } from '../../lib/theme-hex';
|
|
4
4
|
import { fmtNum, fmtDollar } from '../../lib/format';
|
|
5
5
|
|
|
6
6
|
const TIER_COLORS = {
|
|
@@ -38,16 +38,14 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
|
|
|
38
38
|
const radius = (size - 12) / 2;
|
|
39
39
|
const strokeWidth = 5;
|
|
40
40
|
|
|
41
|
-
// Full 360-degree donut
|
|
42
41
|
if (total === 0) {
|
|
43
|
-
// Empty state — full gray ring
|
|
44
42
|
ctx.beginPath();
|
|
45
43
|
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
46
44
|
ctx.strokeStyle = HEX.surface4;
|
|
47
45
|
ctx.lineWidth = strokeWidth;
|
|
48
46
|
ctx.stroke();
|
|
49
47
|
} else {
|
|
50
|
-
let angle = -Math.PI / 2;
|
|
48
|
+
let angle = -Math.PI / 2;
|
|
51
49
|
for (const tier of tiers) {
|
|
52
50
|
const count = byTier[tier] || 0;
|
|
53
51
|
if (count === 0) continue;
|
|
@@ -65,13 +63,13 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
|
|
|
65
63
|
// Center text
|
|
66
64
|
ctx.textAlign = 'center';
|
|
67
65
|
ctx.textBaseline = 'middle';
|
|
68
|
-
ctx.font = `600 ${size * 0.
|
|
66
|
+
ctx.font = `600 ${size * 0.19}px 'JetBrains Mono Variable', monospace`;
|
|
69
67
|
ctx.fillStyle = HEX.text0;
|
|
70
68
|
ctx.fillText(fmtNum(totalDecisions), cx, cy - 3);
|
|
71
69
|
|
|
72
|
-
ctx.font = `500 ${size * 0.
|
|
73
|
-
ctx.fillStyle = HEX.
|
|
74
|
-
ctx.fillText('ROUTES', cx, cy + size * 0.
|
|
70
|
+
ctx.font = `500 ${size * 0.09}px 'JetBrains Mono Variable', monospace`;
|
|
71
|
+
ctx.fillStyle = HEX.text3;
|
|
72
|
+
ctx.fillText('ROUTES', cx, cy + size * 0.13);
|
|
75
73
|
}, [routing, size, total, totalDecisions]);
|
|
76
74
|
|
|
77
75
|
return (
|
|
@@ -82,7 +80,6 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
|
|
|
82
80
|
style={{ width: size, height: size }}
|
|
83
81
|
/>
|
|
84
82
|
|
|
85
|
-
{/* Tier breakdown */}
|
|
86
83
|
<div className="w-full mt-3 space-y-2 max-w-[180px]">
|
|
87
84
|
{tiers.map((tier) => {
|
|
88
85
|
const count = byTier[tier] || 0;
|
|
@@ -90,14 +87,14 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
|
|
|
90
87
|
const pct = total > 0 ? (count / total) * 100 : 0;
|
|
91
88
|
return (
|
|
92
89
|
<div key={tier} className="space-y-0.5">
|
|
93
|
-
<div className="flex items-center gap-2 text-
|
|
90
|
+
<div className="flex items-center gap-2 text-xs font-mono">
|
|
94
91
|
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: TIER_COLORS[tier] }} />
|
|
95
|
-
<span className="text-
|
|
96
|
-
<span className="text-
|
|
97
|
-
<span className="text-
|
|
98
|
-
<span className="text-
|
|
92
|
+
<span className="text-text-3 uppercase tracking-wider flex-1">{TIER_LABELS[tier]}</span>
|
|
93
|
+
<span className="text-text-1 tabular-nums">{count}</span>
|
|
94
|
+
<span className="text-text-4">/</span>
|
|
95
|
+
<span className="text-text-2 tabular-nums">{fmtDollar(cost)}</span>
|
|
99
96
|
</div>
|
|
100
|
-
<div className="h-[2px] bg-
|
|
97
|
+
<div className="h-[2px] bg-surface-0 rounded-full overflow-hidden ml-3.5">
|
|
101
98
|
<div
|
|
102
99
|
className="h-full rounded-full transition-all duration-500"
|
|
103
100
|
style={{ width: `${Math.min(pct, 100)}%`, background: TIER_COLORS[tier] }}
|
|
@@ -108,9 +105,8 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
|
|
|
108
105
|
})}
|
|
109
106
|
</div>
|
|
110
107
|
|
|
111
|
-
{/* Auto-routed badge */}
|
|
112
108
|
{autoRoutedCount > 0 && (
|
|
113
|
-
<div className="mt-2.5 text-
|
|
109
|
+
<div className="mt-2.5 text-2xs font-mono text-text-3 uppercase tracking-wider">
|
|
114
110
|
{autoRoutedCount} auto-routed
|
|
115
111
|
</div>
|
|
116
112
|
)}
|