groove-dev 0.27.141 → 0.27.142
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/daemon/src/api.js +18 -7
- package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +3 -2
- package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
- package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
- package/node_modules/@groove-dev/daemon/src/process.js +5 -16
- package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
- package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +6 -7
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +3 -3
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
- package/node_modules/@groove-dev/gui/src/views/models.jsx +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +18 -7
- package/packages/daemon/src/introducer.js +1 -1
- package/packages/daemon/src/journalist.js +3 -2
- package/packages/daemon/src/keeper.js +2 -2
- package/packages/daemon/src/memory.js +8 -5
- package/packages/daemon/src/process.js +5 -16
- package/packages/daemon/src/rotator.js +25 -8
- package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/packages/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/packages/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +0 -2
- package/packages/gui/src/components/agents/agent-chat.jsx +6 -7
- package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
- package/packages/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
- package/packages/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/packages/gui/src/components/editor/code-editor.jsx +2 -68
- package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/packages/gui/src/stores/groove.js +3 -3
- package/packages/gui/src/views/dashboard.jsx +2 -0
- package/packages/gui/src/views/marketplace.jsx +3 -71
- package/packages/gui/src/views/models.jsx +3 -3
- package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
- package/packages/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/packages/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/packages/gui/src/components/toys/toy-card.jsx +0 -78
- package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
- package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/packages/gui/src/views/toys.jsx +0 -162
|
@@ -6,7 +6,6 @@ import { AgentFileTree } from './agent-file-tree';
|
|
|
6
6
|
import { DiffViewer } from './diff-viewer';
|
|
7
7
|
import { CodeReview } from './code-review';
|
|
8
8
|
import { CodeEditor } from '../editor/code-editor';
|
|
9
|
-
import { AiPanel } from '../editor/ai-panel';
|
|
10
9
|
import { SelectionMenu } from '../editor/selection-menu';
|
|
11
10
|
import { InlinePrompt } from '../editor/inline-prompt';
|
|
12
11
|
import { QuickSearch } from '../editor/quick-search';
|
|
@@ -15,7 +14,7 @@ import { roleColor } from '../../lib/status';
|
|
|
15
14
|
import { MediaViewer, isMediaFile } from '../editor/media-viewer';
|
|
16
15
|
import {
|
|
17
16
|
X, Code2, FileCode, GitCompareArrows,
|
|
18
|
-
ClipboardCheck, Users, PanelLeftOpen,
|
|
17
|
+
ClipboardCheck, Users, PanelLeftOpen, Search,
|
|
19
18
|
} from 'lucide-react';
|
|
20
19
|
|
|
21
20
|
const TREE_DEFAULT = 220;
|
|
@@ -61,7 +60,7 @@ function AgentRail({ agents, activeId, onSelect }) {
|
|
|
61
60
|
);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggleDiff, workspaceSnapshots, onBackToTeam, onToggleReview, reviewMode,
|
|
63
|
+
function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggleDiff, workspaceSnapshots, onBackToTeam, onToggleReview, reviewMode, onCmdP }) {
|
|
65
64
|
const hasSnapshot = activeFile && workspaceSnapshots[activeFile];
|
|
66
65
|
|
|
67
66
|
return (
|
|
@@ -142,19 +141,6 @@ function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggle
|
|
|
142
141
|
<ClipboardCheck size={12} />
|
|
143
142
|
</button>
|
|
144
143
|
</Tooltip>
|
|
145
|
-
<Tooltip content="AI Panel" side="bottom">
|
|
146
|
-
<button
|
|
147
|
-
onClick={onToggleAi}
|
|
148
|
-
className={cn(
|
|
149
|
-
'flex items-center gap-1 px-2 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
|
|
150
|
-
aiOpen
|
|
151
|
-
? 'bg-accent/15 text-accent'
|
|
152
|
-
: 'text-text-3 hover:text-text-1 hover:bg-surface-3',
|
|
153
|
-
)}
|
|
154
|
-
>
|
|
155
|
-
<Sparkles size={12} />
|
|
156
|
-
</button>
|
|
157
|
-
</Tooltip>
|
|
158
144
|
<Tooltip content="Back to Team View" side="bottom">
|
|
159
145
|
<button
|
|
160
146
|
onClick={onBackToTeam}
|
|
@@ -177,10 +163,6 @@ export function WorkspaceMode() {
|
|
|
177
163
|
const toggleReviewMode = useGrooveStore((s) => s.toggleReviewMode);
|
|
178
164
|
const workspaceSnapshots = useGrooveStore((s) => s.workspaceSnapshots);
|
|
179
165
|
const setWorkspaceMode = useGrooveStore((s) => s.setWorkspaceMode);
|
|
180
|
-
const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
|
|
181
|
-
const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
|
|
182
|
-
const aiPanelWidth = useGrooveStore((s) => s.editorAiPanelWidth);
|
|
183
|
-
const setAiPanelWidth = useGrooveStore((s) => s.setEditorAiPanelWidth);
|
|
184
166
|
const setQuickSearchOpen = useGrooveStore((s) => s.setEditorQuickSearchOpen);
|
|
185
167
|
|
|
186
168
|
const editorFiles = useGrooveStore((s) => s.editorFiles);
|
|
@@ -205,9 +187,6 @@ export function WorkspaceMode() {
|
|
|
205
187
|
const treeDragging = useRef(false);
|
|
206
188
|
const startX = useRef(0);
|
|
207
189
|
const startW = useRef(0);
|
|
208
|
-
const aiDragging = useRef(false);
|
|
209
|
-
const aiStartX = useRef(0);
|
|
210
|
-
const aiStartW = useRef(0);
|
|
211
190
|
|
|
212
191
|
useEffect(() => {
|
|
213
192
|
setDiffMode(false);
|
|
@@ -238,24 +217,6 @@ export function WorkspaceMode() {
|
|
|
238
217
|
document.addEventListener('mouseup', onUp);
|
|
239
218
|
}, [treeWidth]);
|
|
240
219
|
|
|
241
|
-
const onAiPanelMouseDown = useCallback((e) => {
|
|
242
|
-
e.preventDefault();
|
|
243
|
-
aiDragging.current = true;
|
|
244
|
-
aiStartX.current = e.clientX;
|
|
245
|
-
aiStartW.current = aiPanelWidth;
|
|
246
|
-
function onMove(e) {
|
|
247
|
-
if (!aiDragging.current) return;
|
|
248
|
-
const delta = aiStartX.current - e.clientX;
|
|
249
|
-
setAiPanelWidth(Math.min(Math.max(aiStartW.current + delta, 280), 600));
|
|
250
|
-
}
|
|
251
|
-
function onUp() {
|
|
252
|
-
aiDragging.current = false;
|
|
253
|
-
document.removeEventListener('mousemove', onMove);
|
|
254
|
-
document.removeEventListener('mouseup', onUp);
|
|
255
|
-
}
|
|
256
|
-
document.addEventListener('mousemove', onMove);
|
|
257
|
-
document.addEventListener('mouseup', onUp);
|
|
258
|
-
}, [aiPanelWidth, setAiPanelWidth]);
|
|
259
220
|
|
|
260
221
|
const handleEditorMouseUp = useCallback(() => {
|
|
261
222
|
const view = editorViewRef.current;
|
|
@@ -357,8 +318,6 @@ export function WorkspaceMode() {
|
|
|
357
318
|
onBackToTeam={() => setWorkspaceMode(false)}
|
|
358
319
|
onToggleReview={toggleReviewMode}
|
|
359
320
|
reviewMode={workspaceReviewMode}
|
|
360
|
-
onToggleAi={toggleAiPanel}
|
|
361
|
-
aiOpen={aiPanelOpen}
|
|
362
321
|
onCmdP={() => setQuickSearchOpen(true)}
|
|
363
322
|
/>
|
|
364
323
|
|
|
@@ -423,16 +382,6 @@ export function WorkspaceMode() {
|
|
|
423
382
|
)}
|
|
424
383
|
</div>
|
|
425
384
|
|
|
426
|
-
{/* AI Panel */}
|
|
427
|
-
{aiPanelOpen && !workspaceReviewMode && (
|
|
428
|
-
<div className="relative flex-shrink-0" style={{ width: aiPanelWidth }}>
|
|
429
|
-
<div
|
|
430
|
-
className="absolute top-0 left-0 bottom-0 w-1 cursor-col-resize hover:bg-accent/30 transition-colors z-10"
|
|
431
|
-
onMouseDown={onAiPanelMouseDown}
|
|
432
|
-
/>
|
|
433
|
-
<AiPanel />
|
|
434
|
-
</div>
|
|
435
|
-
)}
|
|
436
385
|
</div>
|
|
437
386
|
|
|
438
387
|
{/* Quick Search modal */}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { HEX } from '../../lib/theme-hex';
|
|
4
|
+
|
|
5
|
+
const SIZE = 36;
|
|
6
|
+
const STROKE = 3;
|
|
7
|
+
const RADIUS = (SIZE - STROKE) / 2;
|
|
8
|
+
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
9
|
+
const START_ANGLE = -90;
|
|
10
|
+
|
|
11
|
+
function gaugeColor(pct) {
|
|
12
|
+
if (pct > 80) return HEX.danger;
|
|
13
|
+
if (pct > 60) return HEX.warning;
|
|
14
|
+
return HEX.success;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function MiniGauge({ name, pct, threshold }) {
|
|
18
|
+
const color = gaugeColor(pct);
|
|
19
|
+
const dashLen = (pct / 100) * CIRCUMFERENCE;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col items-center gap-0.5" title={`${name}: ${pct}% context used`}>
|
|
23
|
+
<svg width={SIZE} height={SIZE} viewBox={`0 0 ${SIZE} ${SIZE}`}>
|
|
24
|
+
<circle
|
|
25
|
+
cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
|
|
26
|
+
fill="none" strokeWidth={STROKE}
|
|
27
|
+
className="stroke-surface-4"
|
|
28
|
+
/>
|
|
29
|
+
<circle
|
|
30
|
+
cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
|
|
31
|
+
fill="none" strokeWidth={STROKE}
|
|
32
|
+
strokeLinecap="round"
|
|
33
|
+
style={{
|
|
34
|
+
stroke: color,
|
|
35
|
+
strokeDasharray: `${dashLen} ${CIRCUMFERENCE - dashLen}`,
|
|
36
|
+
strokeDashoffset: 0,
|
|
37
|
+
transition: 'stroke-dasharray 0.5s ease',
|
|
38
|
+
}}
|
|
39
|
+
transform={`rotate(${START_ANGLE} ${SIZE / 2} ${SIZE / 2})`}
|
|
40
|
+
/>
|
|
41
|
+
{threshold && (
|
|
42
|
+
<circle
|
|
43
|
+
cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
|
|
44
|
+
fill="none" strokeWidth={1}
|
|
45
|
+
strokeLinecap="butt"
|
|
46
|
+
style={{
|
|
47
|
+
stroke: HEX.purple,
|
|
48
|
+
strokeDasharray: `1 ${CIRCUMFERENCE - 1}`,
|
|
49
|
+
strokeDashoffset: -(threshold / 100) * CIRCUMFERENCE,
|
|
50
|
+
}}
|
|
51
|
+
transform={`rotate(${START_ANGLE} ${SIZE / 2} ${SIZE / 2})`}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
<text
|
|
55
|
+
x={SIZE / 2} y={SIZE / 2 + 1}
|
|
56
|
+
textAnchor="middle" dominantBaseline="central"
|
|
57
|
+
className="fill-text-1 font-mono font-semibold"
|
|
58
|
+
style={{ fontSize: 9 }}
|
|
59
|
+
>
|
|
60
|
+
{pct}
|
|
61
|
+
</text>
|
|
62
|
+
</svg>
|
|
63
|
+
<span className="text-2xs font-mono text-text-3 truncate max-w-[40px] leading-none">{name}</span>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function FleetSummary({ zones }) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex items-center gap-2 text-2xs font-mono">
|
|
71
|
+
<span className="text-success">{zones.healthy}</span>
|
|
72
|
+
<span className="text-text-4">/</span>
|
|
73
|
+
<span className="text-warning">{zones.warning}</span>
|
|
74
|
+
<span className="text-text-4">/</span>
|
|
75
|
+
<span className="text-danger">{zones.critical}</span>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ContextGauges = memo(function ContextGauges({ agentBreakdown }) {
|
|
81
|
+
const alive = (agentBreakdown || []).filter(
|
|
82
|
+
(a) => a.status === 'running' || a.status === 'starting',
|
|
83
|
+
);
|
|
84
|
+
if (alive.length === 0) return null;
|
|
85
|
+
|
|
86
|
+
const zones = { healthy: 0, warning: 0, critical: 0 };
|
|
87
|
+
for (const a of alive) {
|
|
88
|
+
const pct = Math.round((a.contextUsage || 0) * 100);
|
|
89
|
+
if (pct > 80) zones.critical++;
|
|
90
|
+
else if (pct > 60) zones.warning++;
|
|
91
|
+
else zones.healthy++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="px-3 py-2 flex-shrink-0 border-b border-border">
|
|
96
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
97
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Context Health</span>
|
|
98
|
+
<FleetSummary zones={zones} />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="flex items-start gap-2 overflow-x-auto">
|
|
101
|
+
{alive.map((a) => {
|
|
102
|
+
const pct = Math.round((a.contextUsage || 0) * 100);
|
|
103
|
+
const threshold = a.rotationThreshold ? Math.round(a.rotationThreshold * 100) : null;
|
|
104
|
+
return <MiniGauge key={a.id} name={a.name} pct={pct} threshold={threshold} />;
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export { ContextGauges };
|
|
@@ -6,6 +6,65 @@ import { fmtNum, fmtPct } from '../../lib/format';
|
|
|
6
6
|
const TIER_COLORS = { heavy: HEX.danger, medium: HEX.warning, light: HEX.success };
|
|
7
7
|
const TIER_LABELS = { heavy: 'Heavy', medium: 'Medium', light: 'Light' };
|
|
8
8
|
|
|
9
|
+
const DONUT_SIZE = 80;
|
|
10
|
+
const DONUT_STROKE = 6;
|
|
11
|
+
const DONUT_RADIUS = (DONUT_SIZE - DONUT_STROKE) / 2;
|
|
12
|
+
const DONUT_CIRCUMFERENCE = 2 * Math.PI * DONUT_RADIUS;
|
|
13
|
+
|
|
14
|
+
function TierDonut({ byTier, total, tiers }) {
|
|
15
|
+
let offset = 0;
|
|
16
|
+
const segments = [];
|
|
17
|
+
for (const tier of tiers) {
|
|
18
|
+
const count = byTier[tier] || 0;
|
|
19
|
+
if (count === 0) continue;
|
|
20
|
+
const pct = count / total;
|
|
21
|
+
const dashLen = pct * DONUT_CIRCUMFERENCE;
|
|
22
|
+
segments.push({ tier, dashLen, offset });
|
|
23
|
+
offset += dashLen;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const heavyPct = total > 0 ? Math.round(((byTier.heavy || 0) / total) * 100) : 0;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<svg width={DONUT_SIZE} height={DONUT_SIZE} viewBox={`0 0 ${DONUT_SIZE} ${DONUT_SIZE}`} className="flex-shrink-0">
|
|
30
|
+
<circle
|
|
31
|
+
cx={DONUT_SIZE / 2} cy={DONUT_SIZE / 2} r={DONUT_RADIUS}
|
|
32
|
+
fill="none" strokeWidth={DONUT_STROKE}
|
|
33
|
+
className="stroke-surface-4"
|
|
34
|
+
/>
|
|
35
|
+
{segments.map((seg) => (
|
|
36
|
+
<circle
|
|
37
|
+
key={seg.tier}
|
|
38
|
+
cx={DONUT_SIZE / 2} cy={DONUT_SIZE / 2} r={DONUT_RADIUS}
|
|
39
|
+
fill="none" strokeWidth={DONUT_STROKE}
|
|
40
|
+
strokeLinecap="butt"
|
|
41
|
+
style={{
|
|
42
|
+
stroke: TIER_COLORS[seg.tier],
|
|
43
|
+
strokeDasharray: `${seg.dashLen} ${DONUT_CIRCUMFERENCE - seg.dashLen}`,
|
|
44
|
+
strokeDashoffset: -seg.offset,
|
|
45
|
+
}}
|
|
46
|
+
transform={`rotate(-90 ${DONUT_SIZE / 2} ${DONUT_SIZE / 2})`}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
<text
|
|
50
|
+
x={DONUT_SIZE / 2} y={DONUT_SIZE / 2 - 2}
|
|
51
|
+
textAnchor="middle" dominantBaseline="central"
|
|
52
|
+
className="fill-text-0 text-sm font-mono font-semibold"
|
|
53
|
+
>
|
|
54
|
+
{fmtNum(total)}
|
|
55
|
+
</text>
|
|
56
|
+
<text
|
|
57
|
+
x={DONUT_SIZE / 2} y={DONUT_SIZE / 2 + 11}
|
|
58
|
+
textAnchor="middle" dominantBaseline="central"
|
|
59
|
+
className="fill-text-3 font-mono"
|
|
60
|
+
style={{ fontSize: 7 }}
|
|
61
|
+
>
|
|
62
|
+
decisions
|
|
63
|
+
</text>
|
|
64
|
+
</svg>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
9
68
|
const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
|
|
10
69
|
if (!routing) return null;
|
|
11
70
|
|
|
@@ -13,7 +72,6 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
|
|
|
13
72
|
const tiers = ['heavy', 'medium', 'light'];
|
|
14
73
|
const total = tiers.reduce((s, t) => s + (byTier[t] || 0), 0);
|
|
15
74
|
|
|
16
|
-
// Build model usage from agent breakdown
|
|
17
75
|
const modelUsage = {};
|
|
18
76
|
for (const a of (agentBreakdown || [])) {
|
|
19
77
|
const model = a.model || 'default';
|
|
@@ -26,43 +84,27 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
|
|
|
26
84
|
|
|
27
85
|
return (
|
|
28
86
|
<div className="flex flex-col h-full px-3 py-3 overflow-y-auto">
|
|
29
|
-
{/* Tier
|
|
87
|
+
{/* Tier donut + legend */}
|
|
30
88
|
{total > 0 && (
|
|
31
|
-
<div className="
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
<span className="text-2xs font-mono text-text-4 ml-auto tabular-nums">{fmtNum(total)} decisions</span>
|
|
35
|
-
</div>
|
|
36
|
-
{/* Stacked horizontal bar */}
|
|
37
|
-
<div className="h-0.5 bg-surface-2 rounded-sm overflow-hidden flex">
|
|
89
|
+
<div className="flex items-center gap-3 mb-3">
|
|
90
|
+
<TierDonut byTier={byTier} total={total} tiers={tiers} />
|
|
91
|
+
<div className="flex flex-col gap-1.5 flex-1 min-w-0">
|
|
38
92
|
{tiers.map((tier) => {
|
|
39
93
|
const count = byTier[tier] || 0;
|
|
40
94
|
if (count === 0) return null;
|
|
41
95
|
const pct = (count / total) * 100;
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
key={tier}
|
|
45
|
-
className="h-full transition-all duration-500"
|
|
46
|
-
style={{ width: `${pct}%`, background: TIER_COLORS[tier] }}
|
|
47
|
-
title={`${TIER_LABELS[tier]}: ${count} (${Math.round(pct)}%)`}
|
|
48
|
-
/>
|
|
49
|
-
);
|
|
50
|
-
})}
|
|
51
|
-
</div>
|
|
52
|
-
{/* Tier legend */}
|
|
53
|
-
<div className="flex items-center gap-3">
|
|
54
|
-
{tiers.map((tier) => {
|
|
55
|
-
const count = byTier[tier] || 0;
|
|
56
|
-
if (count === 0) return null;
|
|
57
|
-
const pct = total > 0 ? (count / total) * 100 : 0;
|
|
58
96
|
return (
|
|
59
97
|
<div key={tier} className="flex items-center gap-1.5">
|
|
60
98
|
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: TIER_COLORS[tier] }} />
|
|
61
|
-
<span className="text-2xs font-mono text-text-2">{TIER_LABELS[tier]}</span>
|
|
62
|
-
<span className="text-2xs font-mono text-text-4 tabular-nums">{
|
|
99
|
+
<span className="text-2xs font-mono text-text-2 flex-1">{TIER_LABELS[tier]}</span>
|
|
100
|
+
<span className="text-2xs font-mono text-text-4 tabular-nums">{count}</span>
|
|
101
|
+
<span className="text-2xs font-mono text-text-4 tabular-nums w-8 text-right">{fmtPct(pct)}</span>
|
|
63
102
|
</div>
|
|
64
103
|
);
|
|
65
104
|
})}
|
|
105
|
+
{autoRoutedCount > 0 && (
|
|
106
|
+
<div className="text-2xs font-mono text-text-4 mt-0.5">{autoRoutedCount} auto-routed</div>
|
|
107
|
+
)}
|
|
66
108
|
</div>
|
|
67
109
|
</div>
|
|
68
110
|
)}
|
|
@@ -70,12 +112,7 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
|
|
|
70
112
|
{/* Model usage breakdown */}
|
|
71
113
|
{modelEntries.length > 0 && (
|
|
72
114
|
<div className="space-y-1.5 flex-1">
|
|
73
|
-
<
|
|
74
|
-
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Models in Use</span>
|
|
75
|
-
{autoRoutedCount > 0 && (
|
|
76
|
-
<span className="text-2xs font-mono text-text-4 ml-auto">{autoRoutedCount} auto</span>
|
|
77
|
-
)}
|
|
78
|
-
</div>
|
|
115
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Models in Use</span>
|
|
79
116
|
<div className="space-y-1.5">
|
|
80
117
|
{modelEntries.map(([model, usage]) => {
|
|
81
118
|
const barPct = maxModelTokens > 0 ? (usage.tokens / maxModelTokens) * 100 : 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useRef, useEffect, useCallback } from 'react';
|
|
3
|
-
import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter
|
|
4
|
-
import { EditorState, Compartment
|
|
3
|
+
import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter } from '@codemirror/view';
|
|
4
|
+
import { EditorState, Compartment } from '@codemirror/state';
|
|
5
5
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
6
6
|
import { bracketMatching } from '@codemirror/language';
|
|
7
7
|
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
vscodeLight, xcodeLight,
|
|
27
27
|
} from '@uiw/codemirror-themes-all';
|
|
28
28
|
import { useGrooveStore } from '../../stores/groove';
|
|
29
|
-
import { api } from '../../lib/api';
|
|
30
29
|
|
|
31
30
|
const LANGS = {
|
|
32
31
|
javascript: () => javascript({ jsx: true, typescript: false }),
|
|
@@ -79,52 +78,6 @@ export const EDITOR_THEMES = {
|
|
|
79
78
|
basicLight: { label: 'Basic Light', ext: basicLight },
|
|
80
79
|
};
|
|
81
80
|
|
|
82
|
-
// ── Git gutter decorations ───────────────────────────────────
|
|
83
|
-
const setGitLines = StateEffect.define();
|
|
84
|
-
|
|
85
|
-
const gitLinesField = StateField.define({
|
|
86
|
-
create() { return { added: new Set(), modified: new Set(), deleted: new Set() }; },
|
|
87
|
-
update(value, tr) {
|
|
88
|
-
for (const e of tr.effects) {
|
|
89
|
-
if (e.is(setGitLines)) return e.value;
|
|
90
|
-
}
|
|
91
|
-
return value;
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
class GitGutterMarker extends GutterMarker {
|
|
96
|
-
constructor(type) { super(); this.type = type; }
|
|
97
|
-
toDOM() {
|
|
98
|
-
const el = document.createElement('div');
|
|
99
|
-
el.style.width = '3px';
|
|
100
|
-
el.style.height = '100%';
|
|
101
|
-
el.style.borderRadius = '1px';
|
|
102
|
-
if (this.type === 'added') el.style.background = 'var(--color-success)';
|
|
103
|
-
else if (this.type === 'modified') el.style.background = 'var(--color-warning)';
|
|
104
|
-
else el.style.background = 'var(--color-danger)';
|
|
105
|
-
return el;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const addedMarker = new GitGutterMarker('added');
|
|
110
|
-
const modifiedMarker = new GitGutterMarker('modified');
|
|
111
|
-
const deletedMarker = new GitGutterMarker('deleted');
|
|
112
|
-
|
|
113
|
-
const gitGutter = gutter({
|
|
114
|
-
class: 'cm-git-gutter',
|
|
115
|
-
markers(view) {
|
|
116
|
-
const lines = view.state.field(gitLinesField);
|
|
117
|
-
const markers = [];
|
|
118
|
-
for (let i = 1; i <= view.state.doc.lines; i++) {
|
|
119
|
-
const lineStart = view.state.doc.line(i).from;
|
|
120
|
-
if (lines.added.has(i)) markers.push(addedMarker.range(lineStart));
|
|
121
|
-
else if (lines.modified.has(i)) markers.push(modifiedMarker.range(lineStart));
|
|
122
|
-
else if (lines.deleted.has(i)) markers.push(deletedMarker.range(lineStart));
|
|
123
|
-
}
|
|
124
|
-
return RangeSet.of(markers);
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
81
|
const editorChrome = EditorView.theme({
|
|
129
82
|
'&': { fontFamily: 'var(--font-mono)', fontSize: '12px', height: '100%', lineHeight: '1.6' },
|
|
130
83
|
'.cm-scroller': { overflow: 'auto', padding: '4px 0' },
|
|
@@ -139,8 +92,6 @@ const editorChrome = EditorView.theme({
|
|
|
139
92
|
'.cm-search .cm-button, .cm-button': { borderRadius: '4px', padding: '2px 8px', fontSize: '10px', fontFamily: 'var(--font-sans)', cursor: 'pointer', backgroundImage: 'none' },
|
|
140
93
|
'.cm-search br': { display: 'none' },
|
|
141
94
|
'.cm-panel.cm-search [name=close]': { cursor: 'pointer', padding: '0 4px' },
|
|
142
|
-
'.cm-git-gutter': { width: '4px', marginRight: '2px' },
|
|
143
|
-
'.cm-git-gutter .cm-gutterElement': { padding: '0', minWidth: '3px' },
|
|
144
95
|
});
|
|
145
96
|
|
|
146
97
|
function getThemeExt(key) {
|
|
@@ -186,8 +137,6 @@ export function CodeEditor({ content, language, onChange, onSave, onCursorChange
|
|
|
186
137
|
const state = EditorState.create({
|
|
187
138
|
doc: content || '',
|
|
188
139
|
extensions: [
|
|
189
|
-
gitLinesField,
|
|
190
|
-
gitGutter,
|
|
191
140
|
lineNumbers(),
|
|
192
141
|
highlightActiveLine(),
|
|
193
142
|
highlightActiveLineGutter(),
|
|
@@ -220,21 +169,6 @@ export function CodeEditor({ content, language, onChange, onSave, onCursorChange
|
|
|
220
169
|
return () => { view.destroy(); viewRef.current = null; if (externalViewRef) externalViewRef.current = null; };
|
|
221
170
|
}, []);
|
|
222
171
|
|
|
223
|
-
// Fetch and apply git line status when filePath changes
|
|
224
|
-
useEffect(() => {
|
|
225
|
-
const view = viewRef.current;
|
|
226
|
-
if (!view || !filePath) return;
|
|
227
|
-
api.get(`/files/git-line-status?path=${encodeURIComponent(filePath)}`).then((data) => {
|
|
228
|
-
if (!data?.lines) return;
|
|
229
|
-
const lines = {
|
|
230
|
-
added: new Set(data.lines.added || []),
|
|
231
|
-
modified: new Set(data.lines.modified || []),
|
|
232
|
-
deleted: new Set(data.lines.deleted || []),
|
|
233
|
-
};
|
|
234
|
-
view.dispatch({ effects: setGitLines.of(lines) });
|
|
235
|
-
}).catch(() => {});
|
|
236
|
-
}, [filePath, content]);
|
|
237
|
-
|
|
238
172
|
useEffect(() => {
|
|
239
173
|
const view = viewRef.current;
|
|
240
174
|
if (!view) return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { Network, Code2, ChartSpline, Puzzle,
|
|
2
|
+
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, Eye, BookOpen } from 'lucide-react';
|
|
3
3
|
import { cn } from '../../lib/cn';
|
|
4
4
|
import { Tooltip } from '../ui/tooltip';
|
|
5
5
|
import { useGrooveStore } from '../../stores/groove';
|
|
@@ -13,7 +13,6 @@ const BASE_NAV_ITEMS = [
|
|
|
13
13
|
{ id: 'memory', icon: BookOpen, label: 'Memory' },
|
|
14
14
|
{ id: 'teams', icon: Users, label: 'Teams' },
|
|
15
15
|
{ id: 'marketplace', icon: Puzzle, label: 'Marketplace' },
|
|
16
|
-
{ id: 'toys', icon: Gamepad2, label: 'Toys' },
|
|
17
16
|
{ id: 'models', icon: Box, label: 'Models' },
|
|
18
17
|
{ id: 'model-lab', icon: FlaskConical, label: 'Model Lab' },
|
|
19
18
|
];
|
|
@@ -228,7 +228,6 @@ export function TerminalPanel({
|
|
|
228
228
|
<Tooltip content={activeAgent ? `Send to ${agent?.name || 'agent'}` : 'Send to agent'} side="top">
|
|
229
229
|
<button
|
|
230
230
|
onClick={handleSendClick}
|
|
231
|
-
disabled={sending}
|
|
232
231
|
className={cn(
|
|
233
232
|
'flex items-center gap-1.5 px-2 py-1 rounded text-xs font-sans cursor-pointer transition-colors mr-1',
|
|
234
233
|
'bg-accent/15 text-accent hover:bg-accent/25 disabled:opacity-50',
|
|
@@ -1322,7 +1322,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1322
1322
|
},
|
|
1323
1323
|
|
|
1324
1324
|
async _handleKeeperCommand(agentId, message, command) {
|
|
1325
|
-
const rest = message.replace(/\[\w+[-\w]*\]
|
|
1325
|
+
const rest = message.replace(/\[\w+[-\w]*\]/i, '').trim();
|
|
1326
1326
|
const tags = (rest.match(/#[\w/.-]+/g) || []).map(t => t.replace(/^#/, ''));
|
|
1327
1327
|
|
|
1328
1328
|
const addSystemMsg = (text) => {
|
|
@@ -2684,9 +2684,9 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2684
2684
|
|
|
2685
2685
|
async instructAgent(id, message) {
|
|
2686
2686
|
// ── Keeper command interception ─────────────────────────
|
|
2687
|
-
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]
|
|
2687
|
+
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
2688
2688
|
if (keeperCmd) {
|
|
2689
|
-
const handled = await get()._handleKeeperCommand(id, message,
|
|
2689
|
+
const handled = await get()._handleKeeperCommand(id, message, keeperCmd[1].toLowerCase());
|
|
2690
2690
|
if (handled === true) return { status: 'keeper_handled' };
|
|
2691
2691
|
if (handled?.passthrough) {
|
|
2692
2692
|
message = handled.passthrough;
|
|
@@ -4,6 +4,7 @@ import { useGrooveStore } from '../stores/groove';
|
|
|
4
4
|
import { DashboardHeader } from '../components/dashboard/header-bar';
|
|
5
5
|
import { KpiStrip } from '../components/dashboard/kpi-card';
|
|
6
6
|
import { FleetPanel } from '../components/dashboard/fleet-panel';
|
|
7
|
+
import { ContextGauges } from '../components/dashboard/context-gauges';
|
|
7
8
|
import { TokenChart } from '../components/dashboard/token-chart';
|
|
8
9
|
import { CacheRing } from '../components/dashboard/cache-ring';
|
|
9
10
|
import { RoutingChart } from '../components/dashboard/routing-chart';
|
|
@@ -142,6 +143,7 @@ export default function DashboardView() {
|
|
|
142
143
|
<div className="px-3 pt-2.5 pb-1 flex-shrink-0">
|
|
143
144
|
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
|
|
144
145
|
</div>
|
|
146
|
+
<ContextGauges agentBreakdown={agentBreakdown} />
|
|
145
147
|
<FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
|
|
146
148
|
</div>
|
|
147
149
|
|
|
@@ -4,15 +4,13 @@ import { ScrollArea } from '../components/ui/scroll-area';
|
|
|
4
4
|
import { Badge } from '../components/ui/badge';
|
|
5
5
|
import { Button } from '../components/ui/button';
|
|
6
6
|
import { Skeleton } from '../components/ui/skeleton';
|
|
7
|
-
import {
|
|
7
|
+
import { SkillCardSkeleton } from '../components/marketplace/skill-card';
|
|
8
8
|
import { MarketplaceCard } from '../components/marketplace/marketplace-card';
|
|
9
9
|
import { SearchBar } from '../components/marketplace/search-bar';
|
|
10
|
-
import { CategoryBar } from '../components/marketplace/category-bar';
|
|
11
10
|
import { MarketplaceBadge } from '../components/marketplace/marketplace-badge';
|
|
12
11
|
import { StarRating } from '../components/marketplace/star-rating';
|
|
13
12
|
import { PriceBadge } from '../components/marketplace/price-badge';
|
|
14
13
|
import { VerifiedShield } from '../components/marketplace/verified-shield';
|
|
15
|
-
import { markFavorites } from '../components/marketplace/favorites';
|
|
16
14
|
import { api } from '../lib/api';
|
|
17
15
|
import { useToast } from '../lib/hooks/use-toast';
|
|
18
16
|
import { fmtNum, timeAgo } from '../lib/format';
|
|
@@ -22,7 +20,7 @@ import { RepoImport } from '../components/marketplace/repo-import';
|
|
|
22
20
|
import { RepoCard } from '../components/marketplace/repo-card';
|
|
23
21
|
import { RepoNukeDialog } from '../components/marketplace/repo-nuke-dialog';
|
|
24
22
|
import {
|
|
25
|
-
ChevronLeft,
|
|
23
|
+
ChevronLeft, Plug, LogIn,
|
|
26
24
|
Upload, Package, Download, ShoppingBag, RefreshCw, Trash2,
|
|
27
25
|
GitBranch,
|
|
28
26
|
} from 'lucide-react';
|
|
@@ -214,70 +212,6 @@ function SkillDetail({ skill, onBack }) {
|
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
// ── Skills Browse ────────────────────────────────────────
|
|
217
|
-
function SkillsBrowse() {
|
|
218
|
-
const [skills, setSkills] = useState([]);
|
|
219
|
-
const [loading, setLoading] = useState(true);
|
|
220
|
-
const [search, setSearch] = useState('');
|
|
221
|
-
const [category, setCategory] = useState('');
|
|
222
|
-
const [sort, setSort] = useState('popular');
|
|
223
|
-
const [selectedSkill, setSelectedSkill] = useState(null);
|
|
224
|
-
|
|
225
|
-
useEffect(() => {
|
|
226
|
-
setLoading(true);
|
|
227
|
-
const params = new URLSearchParams();
|
|
228
|
-
if (search) params.set('search', search);
|
|
229
|
-
if (category) params.set('category', category);
|
|
230
|
-
if (sort) params.set('sort', sort);
|
|
231
|
-
api.get(`/skills/registry?${params}`)
|
|
232
|
-
.then((d) => setSkills(markFavorites(d.skills || d.items || (Array.isArray(d) ? d : []))))
|
|
233
|
-
.catch(() => setSkills([]))
|
|
234
|
-
.finally(() => setLoading(false));
|
|
235
|
-
}, [search, category, sort]);
|
|
236
|
-
|
|
237
|
-
if (selectedSkill) {
|
|
238
|
-
return <SkillDetail skill={selectedSkill} onBack={() => setSelectedSkill(null)} />;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<ScrollArea className="h-full">
|
|
243
|
-
<div className="px-5 py-4">
|
|
244
|
-
<div className="flex items-center gap-3">
|
|
245
|
-
<div className="w-72">
|
|
246
|
-
<SearchBar value={search} onChange={setSearch} />
|
|
247
|
-
</div>
|
|
248
|
-
<CategoryBar selected={category} onSelect={setCategory} />
|
|
249
|
-
<div className="flex-1" />
|
|
250
|
-
<div className="relative flex-shrink-0">
|
|
251
|
-
<select
|
|
252
|
-
value={sort}
|
|
253
|
-
onChange={(e) => setSort(e.target.value)}
|
|
254
|
-
className="appearance-none font-sans cursor-pointer pr-7 py-2 pl-3 text-xs bg-surface-0 border border-border-subtle rounded text-text-1 focus:outline-none"
|
|
255
|
-
>
|
|
256
|
-
<option value="popular">Popular</option>
|
|
257
|
-
<option value="rating">Top Rated</option>
|
|
258
|
-
<option value="newest">Newest</option>
|
|
259
|
-
<option value="name">A-Z</option>
|
|
260
|
-
</select>
|
|
261
|
-
<ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none" />
|
|
262
|
-
</div>
|
|
263
|
-
<span className="text-2xs text-text-4 font-mono flex-shrink-0">{skills.length}</span>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
<div className="mt-4 grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))' }}>
|
|
267
|
-
{loading
|
|
268
|
-
? Array.from({ length: 8 }).map((_, i) => <SkillCardSkeleton key={i} />)
|
|
269
|
-
: skills.map((s) => <SkillCard key={s.id} skill={s} onClick={setSelectedSkill} />)
|
|
270
|
-
}
|
|
271
|
-
</div>
|
|
272
|
-
|
|
273
|
-
{!loading && skills.length === 0 && (
|
|
274
|
-
<div className="text-center py-16 text-text-4 font-sans text-sm">No skills found.</div>
|
|
275
|
-
)}
|
|
276
|
-
</div>
|
|
277
|
-
</ScrollArea>
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
215
|
// ── Integrations Browse ──────────────────────────────────
|
|
282
216
|
const GOOGLE_IDS = new Set(['gmail', 'google-calendar', 'google-drive', 'google-docs', 'google-sheets', 'google-slides']);
|
|
283
217
|
|
|
@@ -666,10 +600,9 @@ function GitHubBrowse() {
|
|
|
666
600
|
|
|
667
601
|
// ── Main ─────────────────────────────────────────────────
|
|
668
602
|
export default function MarketplaceView() {
|
|
669
|
-
const [tab, setTab] = useState('
|
|
603
|
+
const [tab, setTab] = useState('integrations');
|
|
670
604
|
|
|
671
605
|
const tabs = [
|
|
672
|
-
{ id: 'skills', label: 'Skills', icon: Sparkles },
|
|
673
606
|
{ id: 'integrations', label: 'Integrations', icon: Plug },
|
|
674
607
|
{ id: 'github', label: 'GitHub', icon: GitBranch },
|
|
675
608
|
{ id: 'library', label: 'My Library', icon: Package },
|
|
@@ -701,7 +634,6 @@ export default function MarketplaceView() {
|
|
|
701
634
|
|
|
702
635
|
{/* Content */}
|
|
703
636
|
<div className="flex-1 min-h-0">
|
|
704
|
-
{tab === 'skills' && <SkillsBrowse />}
|
|
705
637
|
{tab === 'integrations' && <IntegrationsBrowse />}
|
|
706
638
|
{tab === 'github' && <GitHubBrowse />}
|
|
707
639
|
{tab === 'library' && <MyLibrary />}
|