groove-dev 0.9.0 → 0.9.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/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-iBqV1c12.js +73 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +137 -74
- package/node_modules/@groove-dev/gui/src/views/AgentTree.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/views/CommandCenter.jsx +212 -333
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/gui/dist/assets/index-iBqV1c12.js +73 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/AgentNode.jsx +137 -74
- package/packages/gui/src/views/AgentTree.jsx +8 -8
- package/packages/gui/src/views/CommandCenter.jsx +212 -333
- package/node_modules/@groove-dev/gui/dist/assets/index-B5E3l1CX.js +0 -73
- package/packages/gui/dist/assets/index-B5E3l1CX.js +0 -73
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>GROOVE</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-iBqV1c12.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CPzm9ZE9.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -1,117 +1,180 @@
|
|
|
1
|
-
// GROOVE GUI — Agent Node Component
|
|
1
|
+
// GROOVE GUI — Agent Node Component
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import React from 'react';
|
|
4
|
+
import React, { useRef, useEffect } from 'react';
|
|
5
5
|
import { Handle, Position } from '@xyflow/react';
|
|
6
|
+
import { useGrooveStore } from '../stores/groove';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
running:
|
|
9
|
-
starting:
|
|
10
|
-
stopped:
|
|
11
|
-
crashed:
|
|
12
|
-
completed:
|
|
13
|
-
killed:
|
|
8
|
+
const STATUS_COLORS = {
|
|
9
|
+
running: '#4ae168',
|
|
10
|
+
starting: '#e5c07b',
|
|
11
|
+
stopped: '#5c6370',
|
|
12
|
+
crashed: '#e06c75',
|
|
13
|
+
completed: '#33afbc',
|
|
14
|
+
killed: '#5c6370',
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
planner: '#
|
|
18
|
-
backend: '#33afbc',
|
|
19
|
-
frontend: '#e5c07b',
|
|
20
|
-
fullstack: '#4ae168',
|
|
21
|
-
testing: '#61afef',
|
|
22
|
-
devops: '#d19a66',
|
|
23
|
-
docs: '#
|
|
17
|
+
const ROLE_BADGES = {
|
|
18
|
+
planner: { bg: '#4ae16820', color: '#4ae168' },
|
|
19
|
+
backend: { bg: '#33afbc20', color: '#33afbc' },
|
|
20
|
+
frontend: { bg: '#e5c07b20', color: '#e5c07b' },
|
|
21
|
+
fullstack: { bg: '#4ae16820', color: '#4ae168' },
|
|
22
|
+
testing: { bg: '#61afef20', color: '#61afef' },
|
|
23
|
+
devops: { bg: '#d19a6620', color: '#d19a66' },
|
|
24
|
+
docs: { bg: '#5c637020', color: '#8b929e' },
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
export default function AgentNode({ data }) {
|
|
27
|
-
const
|
|
28
|
+
const statusColor = STATUS_COLORS[data.status] || STATUS_COLORS.stopped;
|
|
29
|
+
const badge = ROLE_BADGES[data.role] || ROLE_BADGES.docs;
|
|
28
30
|
const alive = data.status === 'running' || data.status === 'starting';
|
|
29
31
|
const sel = data.selected;
|
|
30
|
-
const roleColor = ROLE_COLORS[data.role] || '#33afbc';
|
|
31
32
|
const ctx = Math.round((data.contextUsage || 0) * 100);
|
|
32
33
|
|
|
33
34
|
const tokens = data.tokensUsed > 0
|
|
34
35
|
? data.tokensUsed > 999 ? `${(data.tokensUsed / 1000).toFixed(1)}k` : `${data.tokensUsed}`
|
|
35
36
|
: '0';
|
|
36
37
|
|
|
38
|
+
// Get scope summary for activity text
|
|
39
|
+
const activity = data.scope?.length > 0
|
|
40
|
+
? data.scope[0].replace(/\/\*\*$/, '').replace(/^src\//, '')
|
|
41
|
+
: data.role;
|
|
42
|
+
|
|
37
43
|
return (
|
|
38
44
|
<div style={{
|
|
39
45
|
background: '#282c34',
|
|
40
46
|
border: sel ? '1px solid #33afbc' : '1px solid #3e4451',
|
|
41
|
-
borderRadius:
|
|
42
|
-
width:
|
|
47
|
+
borderRadius: 10,
|
|
48
|
+
width: 210,
|
|
49
|
+
padding: 0,
|
|
43
50
|
cursor: 'pointer',
|
|
44
51
|
fontFamily: "'JetBrains Mono', 'SF Mono', 'Fira Code', Consolas, monospace",
|
|
45
52
|
fontSize: 10,
|
|
46
53
|
overflow: 'hidden',
|
|
47
|
-
transition: 'border-color 0.2s',
|
|
48
54
|
}}>
|
|
49
|
-
{/* Target handle — circular port */}
|
|
50
55
|
<Handle type="target" position={Position.Top} style={{
|
|
51
|
-
background: '#282c34', border:
|
|
56
|
+
background: '#282c34', border: '2px solid #3e4451',
|
|
52
57
|
width: 8, height: 8, borderRadius: '50%', top: -4,
|
|
53
58
|
}} />
|
|
54
59
|
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
</
|
|
66
|
-
{/* Status dot */}
|
|
67
|
-
<div style={{
|
|
68
|
-
width: 6, height: 6, borderRadius: '50%', background: st.color, flexShrink: 0,
|
|
69
|
-
...(alive ? { animation: 'pulse 2s infinite' } : {}),
|
|
70
|
-
}} />
|
|
71
|
-
</div>
|
|
60
|
+
<div style={{ padding: '10px 12px 8px' }}>
|
|
61
|
+
{/* Name + status dot */}
|
|
62
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6 }}>
|
|
63
|
+
<div style={{
|
|
64
|
+
width: 7, height: 7, borderRadius: '50%', background: statusColor, flexShrink: 0,
|
|
65
|
+
...(alive ? { animation: 'pulse 2s infinite' } : {}),
|
|
66
|
+
}} />
|
|
67
|
+
<span style={{ color: '#e6e6e6', fontWeight: 700, fontSize: 12, flex: 1 }}>
|
|
68
|
+
{data.name}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
{/* Role badge */}
|
|
73
|
+
<div style={{ marginBottom: 10 }}>
|
|
74
|
+
<span style={{
|
|
75
|
+
fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1,
|
|
76
|
+
color: badge.color, background: badge.bg,
|
|
77
|
+
padding: '3px 8px', borderRadius: 4,
|
|
78
|
+
display: 'inline-block',
|
|
79
|
+
}}>
|
|
80
|
+
{data.role}
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</span>
|
|
92
|
-
<span style={{ color: '#8b929e', fontSize: 9 }}>
|
|
93
|
-
{ctx}% <span style={{ color: '#5c6370' }}>ctx</span>
|
|
94
|
-
</span>
|
|
95
|
-
</div>
|
|
84
|
+
{/* Metrics */}
|
|
85
|
+
<div style={{
|
|
86
|
+
display: 'flex', justifyContent: 'space-between',
|
|
87
|
+
color: '#5c6370', fontSize: 10, marginBottom: 4,
|
|
88
|
+
}}>
|
|
89
|
+
<span>{tokens} tok</span>
|
|
90
|
+
<span>{ctx}% ctx</span>
|
|
91
|
+
</div>
|
|
96
92
|
|
|
97
|
-
|
|
98
|
-
{alive && (
|
|
93
|
+
{/* Activity text */}
|
|
99
94
|
<div style={{
|
|
100
|
-
|
|
95
|
+
color: '#3e4451', fontSize: 9,
|
|
96
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
101
97
|
}}>
|
|
102
|
-
|
|
103
|
-
width: '200%', height: '100%',
|
|
104
|
-
background: `linear-gradient(90deg, transparent 25%, ${st.color}44 35%, ${st.color} 50%, ${st.color}44 65%, transparent 75%)`,
|
|
105
|
-
animation: 'neuralFlow 2s linear infinite',
|
|
106
|
-
}} />
|
|
98
|
+
{activity}
|
|
107
99
|
</div>
|
|
108
|
-
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Live heartbeat — real-time token line chart */}
|
|
103
|
+
{alive && <Heartbeat agentId={data.id} color={statusColor} />}
|
|
109
104
|
|
|
110
|
-
{/* Source handle — circular port */}
|
|
111
105
|
<Handle type="source" position={Position.Bottom} style={{
|
|
112
|
-
background: '#282c34', border:
|
|
106
|
+
background: '#282c34', border: '2px solid #3e4451',
|
|
113
107
|
width: 8, height: 8, borderRadius: '50%', bottom: -4,
|
|
114
108
|
}} />
|
|
115
109
|
</div>
|
|
116
110
|
);
|
|
117
111
|
}
|
|
112
|
+
|
|
113
|
+
// ── HEARTBEAT — Mini real-time token line chart ──
|
|
114
|
+
|
|
115
|
+
function Heartbeat({ agentId, color }) {
|
|
116
|
+
const canvasRef = useRef();
|
|
117
|
+
const tokenTimeline = useGrooveStore((s) => s.tokenTimeline);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const canvas = canvasRef.current;
|
|
121
|
+
if (!canvas) return;
|
|
122
|
+
|
|
123
|
+
const dpr = window.devicePixelRatio || 1;
|
|
124
|
+
const w = 210;
|
|
125
|
+
const h = 24;
|
|
126
|
+
canvas.width = w * dpr;
|
|
127
|
+
canvas.height = h * dpr;
|
|
128
|
+
const ctx = canvas.getContext('2d');
|
|
129
|
+
ctx.scale(dpr, dpr);
|
|
130
|
+
ctx.clearRect(0, 0, w, h);
|
|
131
|
+
|
|
132
|
+
const pts = tokenTimeline[agentId] || [];
|
|
133
|
+
if (pts.length < 2) {
|
|
134
|
+
// Flat baseline
|
|
135
|
+
ctx.strokeStyle = '#2c313a';
|
|
136
|
+
ctx.lineWidth = 0.5;
|
|
137
|
+
ctx.beginPath();
|
|
138
|
+
ctx.moveTo(0, h / 2);
|
|
139
|
+
ctx.lineTo(w, h / 2);
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Use last 40 data points
|
|
145
|
+
const data = pts.slice(-40);
|
|
146
|
+
const vals = data.map((p) => p.v);
|
|
147
|
+
const min = Math.min(...vals);
|
|
148
|
+
const max = Math.max(...vals);
|
|
149
|
+
const range = max - min || 1;
|
|
150
|
+
|
|
151
|
+
const padY = 4;
|
|
152
|
+
const usableH = h - padY * 2;
|
|
153
|
+
const step = w / (data.length - 1);
|
|
154
|
+
|
|
155
|
+
// Line
|
|
156
|
+
ctx.beginPath();
|
|
157
|
+
data.forEach((p, i) => {
|
|
158
|
+
const x = i * step;
|
|
159
|
+
const y = padY + usableH - ((p.v - min) / range) * usableH;
|
|
160
|
+
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
|
161
|
+
});
|
|
162
|
+
ctx.strokeStyle = color;
|
|
163
|
+
ctx.lineWidth = 1;
|
|
164
|
+
ctx.stroke();
|
|
165
|
+
|
|
166
|
+
// End dot
|
|
167
|
+
const lastX = (data.length - 1) * step;
|
|
168
|
+
const lastY = padY + usableH - ((data[data.length - 1].v - min) / range) * usableH;
|
|
169
|
+
ctx.beginPath();
|
|
170
|
+
ctx.arc(lastX, lastY, 2, 0, Math.PI * 2);
|
|
171
|
+
ctx.fillStyle = color;
|
|
172
|
+
ctx.fill();
|
|
173
|
+
}, [tokenTimeline, agentId, color]);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div style={{ borderTop: '1px solid #2c313a', background: '#1e222a' }}>
|
|
177
|
+
<canvas ref={canvasRef} style={{ display: 'block', width: 210, height: 24 }} />
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
@@ -10,7 +10,7 @@ import AgentNode from '../components/AgentNode';
|
|
|
10
10
|
const nodeTypes = { agent: AgentNode };
|
|
11
11
|
|
|
12
12
|
const MAX_PER_ROW = 4;
|
|
13
|
-
const NODE_X_SPACING =
|
|
13
|
+
const NODE_X_SPACING = 250;
|
|
14
14
|
const NODE_Y_SPACING = 140;
|
|
15
15
|
|
|
16
16
|
function AgentTreeInner() {
|
|
@@ -67,13 +67,13 @@ function AgentTreeInner() {
|
|
|
67
67
|
draggable: false,
|
|
68
68
|
style: {
|
|
69
69
|
background: '#282c34',
|
|
70
|
-
color: '#
|
|
71
|
-
border: '1px solid #
|
|
72
|
-
borderRadius:
|
|
73
|
-
fontWeight:
|
|
74
|
-
fontSize:
|
|
75
|
-
letterSpacing:
|
|
76
|
-
padding: '
|
|
70
|
+
color: '#e6e6e6',
|
|
71
|
+
border: '1px solid #3e4451',
|
|
72
|
+
borderRadius: 24,
|
|
73
|
+
fontWeight: 600,
|
|
74
|
+
fontSize: 11,
|
|
75
|
+
letterSpacing: 6,
|
|
76
|
+
padding: '10px 36px 9px',
|
|
77
77
|
fontFamily: "'JetBrains Mono', 'SF Mono', Consolas, monospace",
|
|
78
78
|
},
|
|
79
79
|
};
|